Add TestDefault trait

This commit is contained in:
fluo10 2025-06-22 11:29:44 +09:00
parent 3a2f53dd46
commit 3356684530
15 changed files with 241 additions and 174 deletions

View file

@ -2,13 +2,15 @@ mod multi_address;
mod peer;
pub use multi_address::{
ActiveModel as MultiAddressActiveModel,
ActiveModel as ActiveMultiAddressModel,
Column as MultiAddressColumn,
Model as MultiAddressModel,
Entity as MultiAddressEntity,
};
pub use peer::{
ActiveModel as PeerActiveModel,
ActiveModel as ActivePeerModel,
Column as PeerColumn,
Model as PeerModel,
Entity as PeerEntity,
};

View file

@ -41,14 +41,15 @@ impl ActiveModel {
#[cfg(test)]
mod tests {
use crate::global::get_or_init_test_cache_database;
use super::*;
use libp2p::identity;
use crate::global::GLOBAL;
#[tokio::test]
async fn check_insert_node() {
let db = crate::global::get_or_init_temporary_main_database().await;
let db = get_or_init_test_cache_database().await;
ActiveModel{
peer_id: Set(identity::Keypair::generate_ed25519().public().to_peer_id().to_string()),

View file

@ -37,14 +37,15 @@ impl ActiveModel {
#[cfg(test)]
mod tests {
use crate::global::get_or_init_test_cache_database;
use super::*;
use libp2p::identity;
use crate::global::GLOBAL;
#[tokio::test]
async fn check_insert_node() {
let db = crate::global::get_or_init_temporary_main_database().await;
let db = get_or_init_test_cache_database().await;
ActiveModel{
peer_id: Set(identity::Keypair::generate_ed25519().public().to_peer_id().to_string()),

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
#[cfg(feature="desktop")]
use clap::Args;
use crate::config::{ConfigError, PartialConfig};
use crate::{config::{ConfigError, PartialConfig}};
use libp2p::mdns::Config;
use serde::{Deserialize, Serialize};
@ -10,14 +10,7 @@ static DATA_DATABASE_NAME: &str = "data.sqlite";
static CACHE_DATABASE_NAME: &str = "cache.sqlite";
#[cfg(any(test, feature="test"))]
static TEST_DATA_DATABASE_PATH: std::sync::LazyLock<tempfile::TempPath> = std::sync::LazyLock::new(|| {
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
temp_path.disable_cleanup(true);
println!("{}", temp_path.as_os_str().to_str().unwrap());
temp_path
});
use crate::tests::{GlobalTestDefault, TestDefault};
#[derive(Debug)]
pub struct StorageConfig {
@ -26,11 +19,6 @@ pub struct StorageConfig {
}
impl StorageConfig {
#[cfg(any(test, feature="test"))]
pub fn new_test() -> Self {
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path().keep().unwrap();
Self { data_directory: temp_path.clone(), cache_directory: temp_path }
}
pub fn get_data_database_path(&self) -> PathBuf{
self.data_directory.join(DATA_DATABASE_NAME)
}
@ -39,6 +27,14 @@ impl StorageConfig {
}
}
#[cfg(any(test, feature="test"))]
impl TestDefault for StorageConfig {
fn test_default() -> Self {
let temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path().keep().unwrap();
Self { data_directory: temp_path.clone(), cache_directory: temp_path }
}
}
impl TryFrom<PartialStorageConfig> for StorageConfig {
type Error = ConfigError;

View file

@ -42,14 +42,15 @@ impl ActiveModel {
#[cfg(test)]
mod tests {
use crate::global::get_or_init_test_data_database;
use super::*;
use libp2p::identity;
use crate::global::GLOBAL;
#[tokio::test]
async fn check_insert_node() {
let db = crate::global::get_or_init_temporary_main_database().await;
let db = get_or_init_test_data_database().await;
ActiveModel{
peer_id: Set(identity::Keypair::generate_ed25519().public().to_peer_id().to_string()),

View file

@ -35,14 +35,15 @@ impl ActiveModel {
#[cfg(test)]
mod tests {
use crate::global::get_or_init_test_data_database;
use super::*;
use uuid::{Timestamp, Uuid};
use crate::global::get_or_init_temporary_main_database;
#[tokio::test]
async fn check_insert_record_deletion() {
let db = get_or_init_temporary_main_database().await;
let db = get_or_init_test_data_database().await;
assert!(ActiveModel{
table_name: Set("test_table".to_string()),

View file

@ -1,6 +1,15 @@
use crate::{config::StorageConfig, error::Error, global::SimpleGlobal};
use tokio::sync::OnceCell;
use uuid::fmt::Simple;
use crate::{config::{P2pConfig, StorageConfig}, error::Error, global::GlobalConstant};
pub static STORAGE_CONFIG: SimpleGlobal<StorageConfig> = SimpleGlobal::const_new();
pub static STORAGE_CONFIG: GlobalConstant<StorageConfig> = GlobalConstant::const_new(stringify!(STORAGE_CONFIG));
pub static P2P_CONFIG: GlobalConstant<P2pConfig> = GlobalConstant::const_new(stringify!(P2P_CONFIG));
#[cfg(test)]
mod tests {
use crate::global::{config::P2P_CONFIG, STORAGE_CONFIG};
#[test]
fn test_global_constant_names() {
assert_eq!(STORAGE_CONFIG.name, stringify!(STORAGE_CONFIG));
assert_eq!(P2P_CONFIG.name, stringify!(P2P_CONFIG));
}
}

View file

@ -5,35 +5,51 @@ use sea_orm_migration::MigratorTrait;
use crate::error::Error;
use tokio::sync::OnceCell;
static DATA_DATABASE_CONNECTION: GlobalDatabaseConnection = GlobalDatabaseConnection::const_new();
static CACHE_DATABASE_CONNECTION: GlobalDatabaseConnection = GlobalDatabaseConnection::const_new();
pub static DATA_DATABASE_CONNECTION: GlobalDatabaseConnection = GlobalDatabaseConnection::const_new(stringify!(DATA_DATABASE_CONNECTION));
pub static CACHE_DATABASE_CONNECTION: GlobalDatabaseConnection = GlobalDatabaseConnection::const_new(stringify!(CACHE_DATABASE_CONNECTION));
struct GlobalDatabaseConnection {
pub struct GlobalDatabaseConnection {
name: &'static str,
inner: OnceCell<DatabaseConnection>
}
impl GlobalDatabaseConnection {
pub const fn const_new() -> Self {
pub const fn const_new(name: &'static str) -> Self {
Self {
name: name,
inner: OnceCell::const_new()
}
}
pub fn get(&'static self) -> Option<&'static DatabaseConnection> {
self.inner.get()
pub fn get(&'static self) -> &'static DatabaseConnection {
self.inner.get().expect(&format!("{} is uninitialized!", self.name))
}
pub fn get_and_unwrap(&'static self) -> &'static DatabaseConnection {
self.get().expect(&format!("{} is uninitialized!", &stringify!(self)))
}
pub async fn get_or_try_init<T, U>(&'static self, path: T, _: U) -> Result<&'static DatabaseConnection, Error>
pub async fn get_or_init<T, U>(&'static self, path: T, _: U) -> &'static DatabaseConnection
where
T: AsRef<Path>,
U: MigratorTrait
{
let url = "sqlite://".to_string() + path.as_ref().to_str().unwrap() + "?mode=rwc";
Ok(self.inner.get_or_try_init(|| async {
self.inner.get_or_try_init(|| async {
let db = Database::connect(&url).await?;
U::up(&db, None).await?;
Ok::<DatabaseConnection, DbErr>(db)
}).await?)
}).await.expect(&format!("Fail to initialize {}!", self.name))
}
}
#[cfg(test)]
pub use tests::*;
#[cfg(test)]
mod tests {
use super::*;
use crate::{cache::migration::CacheMigrator, data::migration::DataMigrator, global::STORAGE_CONFIG, tests::GlobalTestDefault};
pub async fn get_or_init_test_data_database() -> &'static DatabaseConnection{
DATA_DATABASE_CONNECTION.get_or_init(STORAGE_CONFIG.get_or_init_test_default().await.get_data_database_path(), DataMigrator).await
}
pub async fn get_or_init_test_cache_database() -> &'static DatabaseConnection{
CACHE_DATABASE_CONNECTION.get_or_init(STORAGE_CONFIG.get_or_init_test_default().await.get_cache_database_path(), CacheMigrator).await
}
}

View file

@ -1,14 +1,16 @@
use std::{any::type_name, collections::HashMap, net::{IpAddr, Ipv4Addr}, path::{Path, PathBuf}, sync::LazyLock};
use crate::{config::{P2pConfig, PartialP2pConfig, StorageConfig}, error::Error};
use crate::{config::{P2pConfig, PartialP2pConfig, StorageConfig}, error::Error };
#[cfg(any(test, feature="test"))]
use crate::tests::{GlobalTestDefault, TestDefault};
use futures::StreamExt;
use libp2p::{swarm::SwarmEvent, Multiaddr, PeerId};
use sea_orm::{prelude::*, Database};
use sea_orm_migration::MigratorTrait;
use tokio::sync::{OnceCell, RwLock};
use tokio::sync::{OnceCell, RwLock, RwLockReadGuard, RwLockWriteGuard};
mod peers;
pub use peers::GlobalPeers;
pub use peers::PEERS;
mod config;
pub use config::STORAGE_CONFIG;
mod database_connection;
@ -37,13 +39,17 @@ fn uninitialized_message<T>(var: T) -> String {
format!("{} is uninitialized!", &stringify!(var))
}
struct SimpleGlobal<T> {
pub struct GlobalConstant<T> {
pub name: &'static str,
inner: OnceCell<T>
}
impl<T> SimpleGlobal<T> {
pub const fn const_new() -> Self {
Self{inner: OnceCell::const_new()}
impl<T> GlobalConstant<T> {
pub const fn const_new(name: &'static str ) -> Self {
Self{
name: name,
inner: OnceCell::const_new()
}
}
pub async fn get_or_init(&'static self, source: T) -> &'static T {
self.inner.get_or_init(|| async {
@ -58,46 +64,40 @@ impl<T> SimpleGlobal<T> {
}
}
#[cfg(any(test, feature="test"))]
impl<T> GlobalTestDefault<T> for GlobalConstant<T>
where
T: TestDefault + 'static
{
async fn get_or_init_test_default(&'static self) -> &'static T {
self.get_or_init(T::test_default()).await
}
}
struct GlobalRwLock<T> {
pub name: &'static str,
inner: OnceCell<RwLock<T>>
}
impl<T> GlobalRwLock<T> {
pub const fn const_new() -> Self {
Self{inner: OnceCell::const_new()}
pub const fn const_new(name: &'static str) -> Self {
Self{
name: name,
inner: OnceCell::const_new()
}
}
async fn write(&'static self) -> tokio::sync::RwLockWriteGuard<'_ ,T> {
self.get_peers_once_cell().get().expect(UNINITIALIZED_MESSAGE).write().await
pub fn get(&'static self) -> &'static RwLock<T> {
self.inner.get().expect(&format!("{} is uninitialized", self.name))
}
async fn read(&'static self) -> RwLockReadGuard<'_, T> {
self.get_peers_once_cell().get().expect(UNINITIALIZED_MESSAGE).read().await
pub async fn write(&'static self) -> RwLockWriteGuard<'_ ,T> {
self.get().write().await
}
pub async fn read(&'static self) -> RwLockReadGuard<'_, T> {
self.get().read().await
}
}
#[cfg(test)]
pub struct TestGlobal {
pub storage_config: &'static StorageConfig,
pub data_database_connection: &'static DatabaseConnection,
pub cache_database_connection: &'static DatabaseConnection,
}
#[cfg(test)]
mod tests {
use crate::{cache::migration::CacheMigrator, data::migration::DataMigrator};
use super::*;
static TEST_DATA_DIRECTORY: LazyLock<PathBuf> = todo!();
static TEST_DATA_DATABASE_PATH: LazyLock<PathBuf> = todo!();
static TEST_CACHE_DIRECTORY: LazyLock<PathBuf> = todo!();
static TEST_CACHE_DATABASE_PATH: LazyLock<PathBuf> = todo!();
static TEST_STORAGE_CONFIG: LazyLock<StorageConfig> = todo!();
pub async fn get_or_try_init_test() -> TestGlobal {
TestGlobal {
storage_config: get_or_init_storage_config(StorageConfig{data_directory: TEST_DATA_DIRECTORY.clone(), cache_directory: TEST_CACHE_DIRECTORY.clone()}).await,
data_database_connection: get_or_try_init_data_database_connection(&*TEST_DATA_DATABASE_PATH, DataMigrator ).await.unwrap(),
cache_database_connection: get_or_try_init_cache_database_connection(&*TEST_CACHE_DATABASE_PATH, CacheMigrator).await.unwrap(),
}
}
}

View file

@ -5,14 +5,7 @@ use tokio::sync::{OnceCell, RwLock, RwLockReadGuard};
use crate::cache::entity::PeerModel;
static UNINITIALIZED_MESSAGE: &str = "Global peer set uninitialized!";
pub trait GlobalPeers {
fn get_peers_once_cell(&'static self) -> &OnceCell<RwLock<HashSet<PeerModel>>>;
async fn write_peers(&'static self) -> tokio::sync::RwLockWriteGuard<'_ ,HashSet<PeerModel>> {
self.get_peers_once_cell().get().expect(UNINITIALIZED_MESSAGE).write().await
}
async fn read_peers(&'static self) -> RwLockReadGuard<'_, HashSet<PeerModel>> {
self.get_peers_once_cell().get().expect(UNINITIALIZED_MESSAGE).read().await
use super::GlobalRwLock;
pub static PEERS: GlobalRwLock<HashSet<PeerModel>> = GlobalRwLock::const_new(stringify!(PEERS));
}
}

View file

@ -1,6 +1,7 @@
use libp2p::{ identity::Keypair, mdns, ping, swarm};
use sea_orm::{ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter};
use crate::{error::Error, global::GlobalPeers};
use crate::{cache::entity::{ActivePeerModel, PeerColumn, PeerEntity}, error::Error, global::{CACHE_DATABASE_CONNECTION, PEERS}};
#[derive(swarm::NetworkBehaviour)]
#[behaviour(to_swarm = "Event")]
@ -29,20 +30,23 @@ pub enum Event {
}
impl Event {
pub async fn run<T>(self, global: &T)
where
T: GlobalPeers
pub async fn run(&self)
{
match self {
Self::Mdns(x) => {
match x {
mdns::Event::Discovered(e) => {
for peer in e {
global.write_peers().await;
peers.insert(peer.0, peer.1);
for peer in e.iter() {
match PeerEntity::find().filter(PeerColumn::PeerId.contains(&peer.0.to_string())).one(CACHE_DATABASE_CONNECTION.get()).await {
Ok(_) => {}
Err(_) => {
ActivePeerModel{
peer_id: Set(peer.0.to_string()),
..ActivePeerModel::new()
}.insert(CACHE_DATABASE_CONNECTION.get()).await;
}
}
}
let peers = global.read_peers().await;
println!("Peers: {peers:?}");
},
_ => {},
}

View file

@ -20,6 +20,14 @@ pub static TEST_DATABASE_PATH: std::sync::LazyLock<PathBuf> = std::sync::LazyLoc
TEST_DIR_PATH.join("lazy-supplements.sqlite")
});
pub trait TestDefault {
fn test_default() -> Self;
}
pub trait GlobalTestDefault<T: 'static> {
async fn get_or_init_test_default(&'static self) -> &'static T;
}
pub fn test_cbor_serialize_deserialize<T>(src: T)
where T: DeserializeOwned + Serialize + PartialEq + std::fmt::Debug
{

View file

@ -1,77 +0,0 @@
use std::path::PathBuf;
use clap::Args;
use lazy_supplements_core::config::{ConfigError, PartialConfig};
use libp2p::mdns::Config;
use serde::{Deserialize, Serialize};
pub struct DesktopConfig {
pub data_directory: PathBuf,
pub data_database: PathBuf,
pub cache_directory: PathBuf,
pub cache_database: PathBuf,
}
impl TryFrom<PartialDesktopConfig> for DesktopConfig {
type Error = ConfigError;
fn try_from(value: PartialDesktopConfig) -> Result<Self, Self::Error> {
Ok(Self {
data_directory: value.data_directory.ok_or(ConfigError::MissingConfig("data_directory".to_string()))?,
data_database: value.data_database.ok_or(ConfigError::MissingConfig("data_database".to_string()))?,
cache_directory: value.cache_directory.ok_or(ConfigError::MissingConfig("cache_directory".to_string()))?,
cache_database: value.cache_database.ok_or(ConfigError::MissingConfig("cache_database".to_string()))?,
})
}
}
#[derive(Args, Clone, Debug, Deserialize, Serialize)]
pub struct PartialDesktopConfig {
#[arg(long)]
pub data_directory: Option<PathBuf>,
#[arg(long)]
pub data_database: Option<PathBuf>,
#[arg(long)]
pub cache_directory: Option<PathBuf>,
#[arg(long)]
pub cache_database: Option<PathBuf>,
}
impl From<DesktopConfig> for PartialDesktopConfig {
fn from(config: DesktopConfig) -> PartialDesktopConfig {
Self {
data_database: Some(config.data_database),
data_directory: Some(config.data_directory),
cache_database: Some(config.cache_database),
cache_directory: Some(config.cache_directory),
}
}
}
impl PartialConfig<DesktopConfig> for PartialDesktopConfig {
fn empty() -> Self {
Self{
data_database: None,
cache_database: None,
data_directory: None,
cache_directory: None,
}
}
fn default() -> Self {
todo!()
}
fn merge(&mut self, other: Self) {
if let Some(x) = other.data_directory {
self.data_directory = Some(x);
}
if let Some(x) = other.data_database {
self.data_database = Some(x);
}
if let Some(x) = other.cache_directory {
self.cache_directory = Some(x);
}
if let Some(x) = other.cache_database {
self.cache_database = Some(x);
}
}
}

View file

@ -33,7 +33,7 @@ impl From<UnixConfig> for PartialUnixConfig {
}
}
impl PartialConfig<UnixConfig> for PartialUnixConfig {
impl PartialConfig for PartialUnixConfig {
fn empty() -> Self {
Self { socket_path: None }
}

View file

@ -36,3 +36,115 @@ pub static DEFAULT_PARTIAL_CORE_CONFIG: LazyLock<PartialCoreConfig> = LazyLock::
port: Some(0),
}
});
pub struct Global {
pub p2p_config: OnceCell<P2pConfig>,
pub main_database: OnceCell<DatabaseConnection>,
pub cache_database: OnceCell<DatabaseConnection>,
pub peers: OnceCell<RwLock<HashMap<PeerId, Multiaddr>>>,
}
impl Global {
pub fn get_p2p_config(&self) -> Option<&P2pConfig> {
self.p2p_config.get()
}
pub async fn get_or_init_p2p_config(&self, config: P2pConfig) -> &P2pConfig {
self.p2p_config.get_or_init(|| async {config}).await
}
pub async fn get_or_init_peers(&self) -> &RwLock<HashMap<PeerId, Multiaddr>> {
self.peers.get_or_init(|| async {
RwLock::new(HashMap::new())
}).await
}
pub async fn read_peers(&self) -> tokio::sync::RwLockReadGuard<'_, HashMap<PeerId, Multiaddr>>{
self.get_or_init_peers().await.read().await
}
pub async fn write_peers(&self) -> tokio::sync::RwLockWriteGuard<'_, HashMap<PeerId, Multiaddr>>{
self.get_or_init_peers().await.write().await
}
pub async fn launch_swarm(&self) -> Result<(), Error> {
let mut swarm = self.get_p2p_config().unwrap().clone().try_into_swarm().await?;
loop{
let swarm_event = swarm.select_next_some().await;
tokio::spawn(async move{
match swarm_event {
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
SwarmEvent::Behaviour(event) => {
println!("{event:?}");
event.run().await;
},
_ => {}
}
});
}
}
}
impl GlobalDatabase for Global {
fn get_main_database(&self) -> Option<&DatabaseConnection> {
self.main_database.get()
}
async fn get_or_try_init_main_database<T, U>(&self, path: T, _: U) -> Result<&DatabaseConnection, Error>
where
T: AsRef<Path>,
U: MigratorTrait,
{
let url = "sqlite://".to_string() + path.as_ref().to_str().unwrap() + "?mode=rwc";
Ok(self.main_database.get_or_try_init(|| async {
let db = Database::connect(&url).await?;
U::up(&db, None).await?;
Ok::<DatabaseConnection, DbErr>(db)
}).await?)
}
fn get_cache_database(&self) -> Option<&DatabaseConnection> {
self.cache_database.get()
}
async fn get_or_try_init_cache_database<T, U>(&self, path: T, _: U) -> Result<&DatabaseConnection, Error>
where
T: AsRef<Path>,
U: MigratorTrait,
{
let url = "sqlite://".to_string() + path.as_ref().to_str().unwrap() + "?mode=rwc";
Ok(self.cache_database.get_or_try_init(|| async {
let db = Database::connect(&url).await?;
U::up(&db, None).await?;
Ok::<DatabaseConnection, DbErr>(db)
}).await?)
}
}
#[cfg(test)]
pub use tests::{get_or_init_temporary_main_database, get_or_init_temporary_cache_database};
#[cfg(test)]
pub mod tests {
use std::sync::LazyLock;
use sea_orm_migration::MigratorTrait;
use crate::{global::GLOBAL, cache::migration::CacheMigrator, data::migration::MainMigrator};
use super::*;
pub async fn get_or_init_temporary_main_database() -> &'static DatabaseConnection {
GLOBAL.get_or_try_init_temporary_main_database(MainMigrator).await.unwrap()
}
pub async fn get_or_init_temporary_cache_database() -> &'static DatabaseConnection {
GLOBAL.get_or_try_init_temporary_cache_database(CacheMigrator).await.unwrap()
}
#[tokio::test]
async fn connect_main_database () {
let db = get_or_init_temporary_main_database().await;
assert!(db.ping().await.is_ok());
}
#[tokio::test]
async fn connect_cache_database () {
let db = get_or_init_temporary_cache_database().await;
assert!(db.ping().await.is_ok());
}
}