Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ futures = "0.3"
walkdir = "2.4"
rayon = "1.8"
tempfile = "3.8"
rusqlite = "0.37.0"
sha2 = "0.10.9"

[dev-dependencies]
tempfile = "3.8"
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,72 @@ Set timeout and parallel jobs:
bcore-mutation analyze -j 8 -t 300 --survival-threshold 0.3
```

### Storage

Performed during the mutants generation (`mutation` command)

Store generated mutants in the `db` folder (create if does not exists).
Default folder: `mutation.db`:

```bash
bcore-mutation mutate <options> --sqlite <db_name>
```

### Examples:

For a specific file, using the default database(`mutation.db`):

```bash
bcore-mutation mutate -f src/wallet/wallet.cpp --sqlite
```

For a specific PR with custom database(`results.db`):

```bash
bcore-mutation mutate -p 12345 --sqlite results.db
```

### Update Storage

Performed during the mutant analysis (`analyze` command)

Perform full analysis for a specific run id (obligatory):

```bash
bcore-mutation analyze --sqlite --runid <run id number>
```

Perform analysis for a specific file:

```bash
bcore-mutation analyze -f <file name> --sqlite --run_id <run id number>
```

Perform analysis for a specific file with custom command to test:

```bash
bcore-mutation analyze -f <file name> --sqlite --run_id <run id number> -c <command to test>
```

### Examples:

For general analysis, on run id 10:

```bash
bcore-mutation analyze --sqlite --run_id 10
```

Analysis on the muts-pr-wallet-1-150 folder generated on run id 1:

```bash
bcore-mutation analyze -f muts-pr-wallet-1-150 --sqlite --run_id 1
```

Perform analysis for muts-pr-wallet-1-150 folder of run id 2 with custom command `cmake --build build`:

```bash
bcore-mutation analyze -f muts-pr-wallet-1-150 --sqlite --run_id 2 -c "cmake --build build"

## Library Usage

The tool can also be used as a Rust library:
Expand Down
30 changes: 29 additions & 1 deletion src/analyze.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::sqlite::{update_status_mutant, update_command_to_test_mutant};
use crate::error::{MutationError, Result};
use crate::report::generate_report;
use std::fs;
Expand All @@ -13,6 +14,8 @@ pub async fn run_analysis(
jobs: u32,
timeout_secs: u64,
survival_threshold: f64,
db_path: Option<PathBuf>,
run_id: Option<i64>,
) -> Result<()> {
let folders = if let Some(folder_path) = folder {
vec![folder_path]
Expand All @@ -28,14 +31,16 @@ pub async fn run_analysis(
jobs,
timeout_secs,
survival_threshold,
db_path.clone(),
run_id,
)
.await?;
}

Ok(())
}

fn find_mutation_folders() -> Result<Vec<PathBuf>> {
pub fn find_mutation_folders() -> Result<Vec<PathBuf>> {
let mut folders = Vec::new();

for entry in WalkDir::new(".").max_depth(1) {
Expand All @@ -58,6 +63,8 @@ pub async fn analyze_folder(
jobs: u32,
timeout_secs: u64,
survival_threshold: f64,
db_path: Option<PathBuf>,
run_id: Option<i64>,
) -> Result<()> {
let mut num_killed: u64 = 0;
let mut not_killed = Vec::new();
Expand Down Expand Up @@ -125,11 +132,32 @@ pub async fn analyze_folder(

if result {
println!("NOT KILLED ❌");

if let (Some(_), Some(run_id)) = (&db_path, run_id) {
update_status_mutant(
false,
&file_path,
db_path.clone(),
run_id,
)?;
}
not_killed.push(file_name.clone());
} else {
println!("KILLED ✅");

if let (Some(_), Some(run_id)) = (&db_path, run_id) {
update_status_mutant(
true,
&file_path,
db_path.clone(),
run_id,
)?;
}
num_killed += 1
}
if let Some(db_path) = db_path.clone() {
update_command_to_test_mutant(&test_command, &file_path, db_path, run_id.clone().unwrap_or_default())?;
}
}

// Generate report
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub enum MutationError {
#[error("Walkdir error: {0}")]
Walkdir(#[from] walkdir::Error),

#[error("SQLite error: {0}")]
Sqlite(#[from] rusqlite::Error),

#[error("Db path error")]
MissingDbPath,

#[error("Other error: {0}")]
Other(#[from] anyhow::Error),
}
Expand Down
11 changes: 11 additions & 0 deletions src/git_changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ pub async fn get_lines_touched(file_path: &str) -> Result<Vec<usize>> {
Ok(lines)
}

pub fn get_commit_hash() -> Result<String> {

let commit_hash = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
.unwrap_or_else(|_| "unknown".to_string());

Ok(commit_hash)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
//! }
//! ```

pub mod sqlite;
pub mod analyze;
pub mod ast_analysis;
pub mod coverage;
Expand Down
73 changes: 71 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::Error;
use clap::{Parser, Subcommand};
use std::collections::HashMap;
use std::path::PathBuf;
Expand All @@ -10,6 +11,7 @@ mod git_changes;
mod mutation;
mod operators;
mod report;
mod sqlite;

use error::{MutationError, Result};

Expand Down Expand Up @@ -64,6 +66,10 @@ enum Commands {
/// Add custom expert rule for arid node detection
#[arg(long, value_name = "PATTERN")]
add_expert_rule: Option<String>,

/// Optional path to SQLite database file (default: mutation.db)
#[arg(long, value_name = "PATH")]
sqlite: Option<Option<PathBuf>>,
},
/// Analyze mutants
Analyze {
Expand All @@ -86,6 +92,14 @@ enum Commands {
/// Maximum acceptable survival rate (0.3 = 30%)
#[arg(long, default_value = "0.75")]
survival_threshold: f64,

/// Optional path to SQLite database file (default: mutation.db)
#[arg(long, value_name = "PATH")]
sqlite: Option<Option<PathBuf>>,

/// Run ID stored in SQLite
#[arg(long)]
runid: Option<i64>,
},
}

Expand All @@ -105,7 +119,10 @@ async fn main() -> Result<()> {
only_security_mutations,
disable_ast_filtering,
add_expert_rule,
sqlite,
} => {
let mut run_id: i64 = 0;

let skip_lines_map = if let Some(path) = skip_lines {
read_skip_lines(&path)?
} else {
Expand All @@ -126,6 +143,16 @@ async fn main() -> Result<()> {
} else {
None
};

let db_path = match sqlite {
Some(Some(path)) => {
let mut full_path = PathBuf::from("db");
full_path.push(path);
Some(full_path)
}
Some(None) => Some(PathBuf::from("db/mutation.db")),
None => None,
};

if pr != 0 && file.is_some() {
return Err(MutationError::InvalidInput(
Expand All @@ -144,9 +171,14 @@ async fn main() -> Result<()> {
println!("Custom expert rule will be applied: {}", expert_rule);
}

if let Some(ref path) = db_path {
sqlite::check_db(path).map_err(Error::from)?;
run_id = sqlite::store_run(path, if pr == 0 { None } else { Some(pr) }).map_err(Error::from)?;
}

mutation::run_mutation(
if pr == 0 { None } else { Some(pr) },
file,
file.clone(),
one_mutant,
only_security_mutations,
range_lines,
Expand All @@ -157,15 +189,52 @@ async fn main() -> Result<()> {
add_expert_rule,
)
.await?;

if let Some(ref path) = db_path {
sqlite::store_mutants(
path,
run_id,
if pr == 0 { None } else { Some(pr) },
file,
range_lines).map_err(Error::from)?;
}

}
Commands::Analyze {
folder,
timeout,
jobs,
command,
survival_threshold,
sqlite,
runid,
} => {
analyze::run_analysis(folder, command, jobs, timeout, survival_threshold).await?;

let db_path = match sqlite.clone() {
Some(Some(path)) => {
let mut full_path = PathBuf::from("db");
full_path.push(path);
Some(full_path)
}
Some(None) => Some(PathBuf::from("db/mutation.db")),
None => None,
};

if sqlite.is_some() {
if runid.is_none() {
return Err(MutationError::InvalidInput(
"--sqlite requires --runid".to_string(),
));
}

if runid.is_some() && db_path.is_none() {
return Err(MutationError::InvalidInput(
"--runid requires --sqlite".to_string(),
));
}
}

analyze::run_analysis(folder, command, jobs, timeout, survival_threshold, db_path, runid).await?;
}
}

Expand Down
Loading