Skip to content

Commit

Permalink
make tests parallelizable, take db path as args in db functions
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroXbrock committed Feb 4, 2025
1 parent 763c5c3 commit ab3807e
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 29 additions & 42 deletions crates/cli/src/commands/db.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
use contender_core::{db::DbOps, error::ContenderError, Result};
use contender_sqlite::SqliteDb;
use std::{fs, path::PathBuf};

fn get_default_db_path() -> String {
format!(
"{}/.contender/contender.db",
std::env::var("HOME").expect("$HOME not found in environment")
)
}

/// Delete the database file
pub async fn drop_db() -> Result<()> {
// Get the database file path from environment or use default
let db_path = std::env::var("CONTENDER_DB_PATH").unwrap_or_else(|_| get_default_db_path());

pub async fn drop_db(db_path: &str) -> Result<()> {
// Check if file exists before attempting to remove
if fs::metadata(&db_path).is_ok() {
fs::remove_file(&db_path).map_err(|e| {
if fs::metadata(db_path).is_ok() {
fs::remove_file(db_path).map_err(|e| {
ContenderError::DbError("Failed to delete database file", Some(e.to_string()))
})?;
println!("Database file '{}' has been deleted.", db_path);
Expand All @@ -26,9 +17,12 @@ pub async fn drop_db() -> Result<()> {
}

/// Reset the database by dropping it and recreating tables
pub async fn reset_db(db: &impl DbOps) -> Result<()> {
pub async fn reset_db(db_path: &str) -> Result<()> {
// Drop the database
drop_db().await?;
drop_db(db_path).await?;

// create a new empty file at db_path (to avoid errors when creating tables)
let db = SqliteDb::from_file(db_path).expect("failed to open contender DB file");

// Recreate tables
db.create_tables()?;
Expand All @@ -37,10 +31,7 @@ pub async fn reset_db(db: &impl DbOps) -> Result<()> {
}

/// Export the database to a file
pub async fn export_db(out_path: PathBuf) -> Result<()> {
// Get the source database path
let src_path = std::env::var("CONTENDER_DB_PATH").unwrap_or_else(|_| get_default_db_path());

pub async fn export_db(src_path: &str, target_path: PathBuf) -> Result<()> {
// Ensure source database exists
if !fs::metadata(&src_path).is_ok() {
return Err(ContenderError::DbError(
Expand All @@ -50,14 +41,14 @@ pub async fn export_db(out_path: PathBuf) -> Result<()> {
}

// Copy the database file to the target location
fs::copy(&src_path, &out_path)
fs::copy(&src_path, &target_path)
.map_err(|e| ContenderError::DbError("Failed to export database", Some(e.to_string())))?;
println!("Database exported to '{}'", out_path.display());
println!("Database exported to '{}'", target_path.display());
Ok(())
}

/// Import the database from a file
pub async fn import_db(src_path: PathBuf) -> Result<()> {
pub async fn import_db(src_path: PathBuf, target_path: &str) -> Result<()> {
// Ensure source file exists
if !src_path.exists() {
return Err(ContenderError::DbError(
Expand All @@ -66,9 +57,6 @@ pub async fn import_db(src_path: PathBuf) -> Result<()> {
));
}

// Get the target database path
let target_path = std::env::var("CONTENDER_DB_PATH").unwrap_or_else(|_| get_default_db_path());

// If target exists, create a backup
if fs::metadata(&target_path).is_ok() {
let backup_path = format!("{}.backup", target_path);
Expand All @@ -90,66 +78,65 @@ pub async fn import_db(src_path: PathBuf) -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use contender_core::db::MockDb;
use std::env;
use tempfile::TempDir;

// NOTE: These tests need to be ran sequentially i.e. `--test-threads=1`
// This is because the tests are using the same database file.
fn setup_test_env() -> (TempDir, String) {
/// Creates a temp directory containing a database file with the given name.
///
/// Returns the temp directory and the full path to the database file.
fn setup_test_env(name: &str) -> (TempDir, String) {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let db_path = temp_dir
.path()
.join("test.db")
.join(&format!("test_{}.db", name))
.to_str()
.unwrap()
.to_string();
env::set_var("CONTENDER_DB_PATH", &db_path);

(temp_dir, db_path)
}

#[tokio::test]
async fn test_drop_db() {
let (_temp_dir, db_path) = setup_test_env();
let (_temp_dir, db_path) = setup_test_env("drop");

// Create a dummy file
fs::write(&db_path, "test data").expect("Failed to write test file");
assert!(fs::metadata(&db_path).is_ok());

// Test dropping the database
drop_db().await.expect("Failed to drop database");
drop_db(&db_path).await.expect("Failed to drop database");
assert!(fs::metadata(&db_path).is_err());
}

#[tokio::test]
async fn test_reset_db() {
let (_temp_dir, db_path) = setup_test_env();
let (_temp_dir, db_path) = setup_test_env("reset");

// Create a mock database
let mock_db = MockDb;
fs::write(&db_path, "testing").expect("Failed to write test file");

// Test resetting the database
reset_db(&mock_db).await.expect("Failed to reset database");
reset_db(&db_path).await.expect("Failed to reset database");
assert!(fs::metadata(&db_path).is_err()); // Should be dropped
}

#[tokio::test]
async fn test_export_import_db() {
let (temp_dir, db_path) = setup_test_env();
let (temp_dir, db_path) = setup_test_env("export_import");

// Create a dummy database file
fs::write(&db_path, "test database content").expect("Failed to write test file");

// Test export
let export_path = temp_dir.path().join("export.db");
export_db(export_path.clone())
let exported_path = temp_dir.path().join("export.db");
export_db(&db_path, exported_path.clone())
.await
.expect("Failed to export database");
assert!(export_path.exists());
assert!(exported_path.exists());

// Test import
fs::remove_file(&db_path).expect("Failed to remove original db");
import_db(export_path)
import_db(exported_path, &db_path)
.await
.expect("Failed to import database");
assert!(fs::metadata(&db_path).is_ok());
Expand Down
19 changes: 8 additions & 11 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@ use commands::{ContenderCli, ContenderSubcommand, DbCommand, SpamCommandArgs};
use contender_core::{db::DbOps, generator::RandSeed};
use contender_sqlite::SqliteDb;
use rand::Rng;
use util::data_dir;
use util::{data_dir, db_file};

static DB: LazyLock<SqliteDb> = std::sync::LazyLock::new(|| {
let path = &format!(
"{}/{}",
data_dir().expect("failed to get data directory"),
"contender.db"
);
let path = db_file().expect("failed to get DB file path");
println!("opening DB at {}", path);
SqliteDb::from_file(path).expect("failed to open contender DB file")
SqliteDb::from_file(&path).expect("failed to open contender DB file")
});

#[tokio::main]
Expand All @@ -27,6 +23,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = DB.create_tables(); // ignore error; tables already exist
let db = DB.clone();
let data_path = data_dir()?;
let db_path = db_file()?;

let seed_path = format!("{}/seed", &data_path);
if !std::path::Path::new(&seed_path).exists() {
Expand All @@ -44,10 +41,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

match args.command {
ContenderSubcommand::Db { command } => match command {
DbCommand::Drop => commands::drop_db().await?,
DbCommand::Reset => commands::reset_db(&db).await?,
DbCommand::Export { out_path } => commands::export_db(out_path).await?,
DbCommand::Import { src_path } => commands::import_db(src_path).await?,
DbCommand::Drop => commands::drop_db(&db_path).await?,
DbCommand::Reset => commands::reset_db(&db_path).await?,
DbCommand::Export { out_path } => commands::export_db(&db_path, out_path).await?,
DbCommand::Import { src_path } => commands::import_db(src_path, &db_path).await?,
},

ContenderSubcommand::Setup {
Expand Down
6 changes: 6 additions & 0 deletions crates/cli/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ pub fn data_dir() -> Result<String, Box<dyn std::error::Error>> {
Ok(dir)
}

/// Returns path to default contender DB file.
pub fn db_file() -> Result<String, Box<dyn std::error::Error>> {
let data_path = data_dir()?;
Ok(format!("{}/contender.db", data_path))
}

#[cfg(test)]
mod test {
use std::str::FromStr;
Expand Down

0 comments on commit ab3807e

Please sign in to comment.