Skip to content

Commit c0ffeef

Browse files
feat: assign mutator tests
1 parent c0ffee8 commit c0ffeef

File tree

8 files changed

+181
-66
lines changed

8 files changed

+181
-66
lines changed

crates/forge/src/mutation/mod.rs

+9-22
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,8 @@ pub struct MutationHandler {
2929
}
3030

3131
impl MutationHandler {
32-
pub fn new(
33-
contract_to_mutate: PathBuf,
34-
config: Arc<foundry_config::Config>,
35-
) -> Self {
36-
Self {
37-
contract_to_mutate,
38-
src: Arc::default(),
39-
mutations: vec![],
40-
config,
41-
temp_dir: None,
42-
}
32+
pub fn new(contract_to_mutate: PathBuf, config: Arc<foundry_config::Config>) -> Self {
33+
Self { contract_to_mutate, src: Arc::default(), mutations: vec![], config, temp_dir: None }
4334
}
4435

4536
/// Keep the source contract in memory (in the hashmap), as we'll use it to create the mutants
@@ -65,8 +56,8 @@ impl MutationHandler {
6556
})?;
6657

6758
let ast = parser.parse_file().map_err(|e| e.emit())?;
68-
69-
let mut mutant_visitor = MutantVisitor { mutation_to_conduct: Vec::new()};
59+
60+
let mut mutant_visitor = MutantVisitor { mutation_to_conduct: Vec::new() };
7061
mutant_visitor.visit_source_unit(&ast);
7162
self.mutations.extend(mutant_visitor.mutation_to_conduct);
7263

@@ -162,16 +153,12 @@ impl MutationHandler {
162153
let ty = entry.file_type()?;
163154

164155
if ty.is_dir() {
165-
Self::copy_dir_except(
166-
entry.path(),
167-
dst.as_ref().join(entry.file_name()),
168-
except,
169-
)?;
156+
Self::copy_dir_except(entry.path(), dst.as_ref().join(entry.file_name()), except)?;
170157
} else if entry.file_name() != except.file_name().unwrap_or_default() {
171-
// std::os::unix::fs::symlink(entry.path(),
172-
// &dst.as_ref().join(entry.file_name()))?; // and for windows, would be
173-
// std::os::windows::fs::symlink_file
174-
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
158+
// std::os::unix::fs::symlink(entry.path(),
159+
// &dst.as_ref().join(entry.file_name()))?; // and for windows, would be
160+
// std::os::windows::fs::symlink_file
161+
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
175162
}
176163
}
177164
Ok(())

crates/forge/src/mutation/mutant.rs

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
// Generate mutants then run tests (reuse the whole unit test flow for now, including compilation to
22
// select mutants) Use Solar:
3-
use solar_parse::ast::{BinOpKind, LitKind, Span, UnOpKind};
4-
use std::path::PathBuf ;
5-
use std::fmt::Display;
63
use super::visitor::AssignVarTypes;
4+
use solar_parse::ast::{BinOpKind, LitKind, Span, UnOpKind};
5+
use std::{fmt::Display, path::PathBuf};
76

87
/// Wraps an unary operator mutated, to easily store pre/post-fix op swaps
98
#[derive(Debug)]
@@ -58,9 +57,8 @@ pub enum MutationType {
5857
// /// For a if(x) condition x:
5958
// /// replace x with true; replace x with false
6059
// This mutation is not used anymore, as we mutate the condition as an expression,
61-
// which will creates true/false mutant as well as more complex conditions (eg if(foo++ > --bar)
62-
// ) IfStatementMutation,
63-
60+
// which will creates true/false mutant as well as more complex conditions (eg if(foo++ >
61+
// --bar) ) IfStatementMutation,
6462
/// For a require(x) condition:
6563
/// replace x with true; replace x with false
6664
// Same as for IfStatementMutation, the expression inside the require is mutated as an
@@ -104,19 +102,15 @@ impl MutationType {
104102
Self::ElimDelegate => "ElimDelegate".to_string(),
105103
Self::FunctionCall => "FunctionCall".to_string(),
106104
Self::Require => "Require".to_string(),
107-
Self::SwapArgumentsFunction => {
108-
"SwapArgumentsFunction".to_string()
109-
}
110-
Self::SwapArgumentsOperator => {
111-
"SwapArgumentsOperator".to_string()
112-
}
105+
Self::SwapArgumentsFunction => "SwapArgumentsFunction".to_string(),
106+
Self::SwapArgumentsOperator => "SwapArgumentsOperator".to_string(),
113107
Self::UnaryOperator(mutated) => {
114108
// avoid operator in tmp dir name
115109
format!("{}_{:?}", "UnaryOperator", mutated.resulting_op_kind)
116110
}
117111
}
118112
}
119-
}
113+
}
120114

121115
impl Display for MutationType {
122116
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -167,4 +161,4 @@ impl Mutant {
167161
self.mutation.get_name()
168162
)
169163
}
170-
}
164+
}

crates/forge/src/mutation/mutators/assignement_mutator.rs

+19-21
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ impl Mutator for AssignmentMutator {
2727
AssignVarTypes::Literal(lit) => match lit {
2828
LitKind::Bool(val) => Ok(vec![Mutant {
2929
span,
30-
mutation: MutationType::Assignment(AssignVarTypes::Literal(
31-
LitKind::Bool(!val),
32-
)),
30+
mutation: MutationType::Assignment(AssignVarTypes::Literal(LitKind::Bool(
31+
!val,
32+
))),
3333
path: PathBuf::default(),
3434
}]),
3535
LitKind::Number(val) => Ok(vec![
@@ -52,24 +52,22 @@ impl Mutator for AssignmentMutator {
5252
eyre::bail!("AssignementMutator: unexpected literal kind: {:?}", lit)
5353
}
5454
},
55-
AssignVarTypes::Identifier(ident) => {
56-
Ok(vec![
57-
Mutant {
58-
span,
59-
mutation: MutationType::Assignment(AssignVarTypes::Literal(
60-
LitKind::Number(num_bigint::BigInt::ZERO),
61-
)),
62-
path: PathBuf::default(),
63-
},
64-
Mutant {
65-
span,
66-
mutation: MutationType::Assignment(AssignVarTypes::Identifier(
67-
format!("-{ident}"),
68-
)),
69-
path: PathBuf::default(),
70-
},
71-
])
72-
}
55+
AssignVarTypes::Identifier(ident) => Ok(vec![
56+
Mutant {
57+
span,
58+
mutation: MutationType::Assignment(AssignVarTypes::Literal(LitKind::Number(
59+
num_bigint::BigInt::ZERO,
60+
))),
61+
path: PathBuf::default(),
62+
},
63+
Mutant {
64+
span,
65+
mutation: MutationType::Assignment(AssignVarTypes::Identifier(format!(
66+
"-{ident}"
67+
))),
68+
path: PathBuf::default(),
69+
},
70+
]),
7371
}
7472
}
7573

crates/forge/src/mutation/mutators/elim_delegate_mutator.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use crate::mutation::mutant::{Mutant, MutationType};
33

44
use eyre::Result;
55
use solar_parse::ast::ExprKind;
6-
use std::path::PathBuf;
7-
use std::fmt::Display;
6+
use std::{fmt::Display, path::PathBuf};
87

98
pub struct ElimDelegateMutator;
109

@@ -27,12 +26,13 @@ impl Mutator for ElimDelegateMutator {
2726
.and_then(|callee| match &callee.kind {
2827
ExprKind::Member(_, ident) => Some(ident),
2928
_ => None,
30-
}).is_some_and(|ident| ident.to_string() == "delegatecall")
29+
})
30+
.is_some_and(|ident| ident.to_string() == "delegatecall")
3131
}
3232
}
3333

3434
impl Display for ElimDelegateMutator {
3535
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3636
write!(f, "")
3737
}
38-
}
38+
}

crates/forge/src/mutation/mutators/mod.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,9 @@ impl<'a> MutationContextBuilder<'a> {
6060
pub fn build(self) -> Result<MutationContext<'a>, &'static str> {
6161
let span = self.span.ok_or("Span is required for MutationContext")?;
6262

63-
Ok(MutationContext {
64-
span,
65-
expr: self.expr,
66-
var_definition: self.var_definition,
67-
})
63+
Ok(MutationContext { span, expr: self.expr, var_definition: self.var_definition })
6864
}
6965
}
66+
67+
#[cfg(test)]
68+
mod tests;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use crate::mutation::{
2+
mutant::{Mutant, MutationType},
3+
mutators::{assignement_mutator::AssignmentMutator, MutationContext, Mutator},
4+
visitor::AssignVarTypes,
5+
};
6+
use solar_parse::{
7+
ast::{
8+
Arena, ElementaryType, Expr, ExprKind, Ident, Lit, LitKind, Span, Symbol, Type, TypeKind,
9+
VariableDefinition,
10+
},
11+
interface::BytePos,
12+
};
13+
14+
use num_bigint::BigInt;
15+
use std::path::PathBuf;
16+
17+
use crate::mutation::Session;
18+
19+
fn create_span(start: u32, end: u32) -> Span {
20+
Span::new(BytePos(start), BytePos(end))
21+
}
22+
23+
fn create_ident<'ident>(ident: String) -> ExprKind<'ident> {
24+
ExprKind::Ident(Ident::from_str(&ident))
25+
}
26+
27+
#[test]
28+
fn test_is_applicable_for_assign_expr() {
29+
let sess = Session::builder().with_silent_emitter(None).build();
30+
31+
let _ = sess.enter(|| -> solar_parse::interface::Result<()> {
32+
let arena = Arena::new();
33+
let span = create_span(10, 20);
34+
35+
// x = 23
36+
let left = arena.alloc(Expr { kind: create_ident("x".into()), span });
37+
38+
let mut val = Lit { span, symbol: Symbol::DUMMY, kind: LitKind::Number(23.into()) };
39+
40+
let right = arena.alloc(Expr { kind: ExprKind::Lit(&mut val, None), span });
41+
42+
let expr = arena.alloc(Expr { kind: ExprKind::Assign(left, None, right), span });
43+
44+
let context = MutationContext { expr: Some(expr), var_definition: None, span };
45+
46+
let mutator = AssignmentMutator;
47+
assert!(mutator.is_applicable(&context));
48+
49+
Ok(())
50+
});
51+
}
52+
53+
#[test]
54+
fn test_is_applicable_for_var_definition() {
55+
let arena = Arena::new();
56+
let span = create_span(10, 20);
57+
58+
let lit = arena.alloc(Lit { span, symbol: Symbol::DUMMY, kind: LitKind::Number(23.into()) });
59+
60+
let init = arena.alloc(Expr { kind: ExprKind::Lit(lit, None), span });
61+
62+
let var_def = VariableDefinition {
63+
name: None,
64+
initializer: Some(init),
65+
span,
66+
ty: Type { kind: TypeKind::Elementary(ElementaryType::Bool), span },
67+
visibility: None,
68+
mutability: None,
69+
data_location: None,
70+
override_: None,
71+
indexed: false,
72+
};
73+
74+
let context = MutationContext { expr: None, var_definition: Some(&var_def), span };
75+
76+
let mutator = AssignmentMutator;
77+
assert!(mutator.is_applicable(&context));
78+
}
79+
80+
#[test]
81+
fn test_is_not_applicable_no_initializer() {
82+
let arena = Arena::new();
83+
let span = create_span(10, 20);
84+
85+
let var_def = VariableDefinition {
86+
name: None,
87+
initializer: None,
88+
span,
89+
ty: Type { kind: TypeKind::Elementary(ElementaryType::Bool), span },
90+
visibility: None,
91+
mutability: None,
92+
data_location: None,
93+
override_: None,
94+
indexed: false,
95+
};
96+
97+
let context = MutationContext { expr: None, var_definition: Some(&var_def), span };
98+
99+
let mutator = AssignmentMutator;
100+
assert!(!mutator.is_applicable(&context));
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::mutation::{
2+
mutant::{Mutant, MutationType},
3+
mutators::{assignement_mutator::AssignmentMutator, MutationContext, Mutator},
4+
visitor::AssignVarTypes,
5+
};
6+
use solar_parse::{
7+
ast::{Arena, Expr, ExprKind, Ident, Lit, LitKind, Span, Symbol},
8+
interface::BytePos,
9+
};
10+
11+
use num_bigint::BigInt;
12+
use std::path::PathBuf;
13+
14+
use crate::mutation::Session;
15+
16+
// Create a span for test use
17+
pub fn create_span(start: u32, end: u32) -> Span {
18+
Span::new(BytePos(start), BytePos(end))
19+
}
20+
21+
// Create identifier with given name
22+
pub fn create_ident(name: &str) -> Ident {
23+
Ident::from_str(name)
24+
}
25+
26+
// Create number literal
27+
pub fn create_number_lit(value: u32, span: Span) -> Lit {
28+
Lit { span, symbol: Symbol::DUMMY, kind: LitKind::Number(value.into()) }
29+
}
30+
31+
// Create boolean literal
32+
pub fn create_bool_lit(value: bool, span: Span) -> Lit {
33+
Lit { span, symbol: Symbol::DUMMY, kind: LitKind::Bool(value) }
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod assignement_mutator_test;
2+
mod ast_helper;

0 commit comments

Comments
 (0)