Skip to content

Commit

Permalink
test: add fuzz tests for column data type alteration (#4076)
Browse files Browse the repository at this point in the history
* feat: support make fuzz-stable in Makefile

* test: add fuzz tests for column data type alteration

* fix: optimize code by cr
  • Loading branch information
realtaobo committed Jun 4, 2024
1 parent 98c19ed commit dd06e10
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ nextest: ## Install nextest tools.
sqlness-test: ## Run sqlness test.
cargo sqlness

# Run fuzz test ${FUZZ_TARGET}.
RUNS ?= 1
FUZZ_TARGET ?= fuzz_alter_table
.PHONY: fuzz
fuzz:
cargo fuzz run ${FUZZ_TARGET} --fuzz-dir tests-fuzz -D -s none -- -runs=${RUNS}

.PHONY: check
check: ## Cargo check all the targets.
cargo check --workspace --all-targets --all-features
Expand Down
1 change: 1 addition & 0 deletions tests-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ sqlx = { version = "0.6", features = [
"postgres",
"chrono",
] }
strum.workspace = true
tinytemplate = "1.2"
tokio = { workspace = true }

Expand Down
6 changes: 6 additions & 0 deletions tests-fuzz/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ impl TableContext {
self.name = new_table_name;
Ok(self)
}
AlterTableOperation::ModifyDataType { column } => {
if let Some(idx) = self.columns.iter().position(|col| col.name == column.name) {
self.columns[idx].column_type = column.column_type;
}
Ok(self)
}
}
}

Expand Down
48 changes: 46 additions & 2 deletions tests-fuzz/src/generator/alter_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Generat
use crate::ir::alter_expr::{AlterTableExpr, AlterTableOperation};
use crate::ir::create_expr::ColumnOption;
use crate::ir::{
droppable_columns, generate_columns, generate_random_value, ColumnTypeGenerator, Ident,
droppable_columns, generate_columns, generate_random_value, modifiable_columns, Column,
ColumnTypeGenerator, Ident,
};

fn add_column_options_generator<R: Rng>(
Expand Down Expand Up @@ -157,6 +158,39 @@ impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprRenameGenerator<R> {
}
}

/// Generates the [AlterTableOperation::ModifyDataType] of [AlterTableExpr].
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct AlterExprModifyDataTypeGenerator<R: Rng> {
table_ctx: TableContextRef,
#[builder(default = "Box::new(ColumnTypeGenerator)")]
column_type_generator: ConcreteDataTypeGenerator<R>,
}

impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprModifyDataTypeGenerator<R> {
type Error = Error;

fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
let modifiable = modifiable_columns(&self.table_ctx.columns);
let changed = modifiable[rng.gen_range(0..modifiable.len())].clone();
let mut to_type = self.column_type_generator.gen(rng);
while !changed.column_type.can_arrow_type_cast_to(&to_type) {
to_type = self.column_type_generator.gen(rng);
}

Ok(AlterTableExpr {
table_name: self.table_ctx.name.clone(),
alter_options: AlterTableOperation::ModifyDataType {
column: Column {
name: changed.name,
column_type: to_type,
options: vec![],
},
},
})
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;
Expand Down Expand Up @@ -200,13 +234,23 @@ mod tests {
assert_eq!(expected, serialized);

let expr = AlterExprDropColumnGeneratorBuilder::default()
.table_ctx(table_ctx)
.table_ctx(table_ctx.clone())
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"DropColumn":{"name":{"value":"cUmquE","quote_style":null}}}}"#;
assert_eq!(expected, serialized);

let expr = AlterExprModifyDataTypeGeneratorBuilder::default()
.table_ctx(table_ctx)
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"ModifyDataType":{"column":{"name":{"value":"toTAm","quote_style":null},"column_type":{"Int64":{}},"options":[]}}}}"#;
assert_eq!(expected, serialized);
}
}
14 changes: 14 additions & 0 deletions tests-fuzz/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,20 @@ pub fn droppable_columns(columns: &[Column]) -> Vec<&Column> {
.collect::<Vec<_>>()
}

/// Returns columns that can use the alter table modify command
pub fn modifiable_columns(columns: &[Column]) -> Vec<&Column> {
columns
.iter()
.filter(|column| {
!column.options.iter().any(|option| {
option == &ColumnOption::PrimaryKey
|| option == &ColumnOption::TimeIndex
|| option == &ColumnOption::NotNull
})
})
.collect::<Vec<_>>()
}

/// Generates [ColumnOption] for [Column].
pub fn column_options_generator<R: Rng>(
rng: &mut R,
Expand Down
2 changes: 2 additions & 0 deletions tests-fuzz/src/ir/alter_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ pub enum AlterTableOperation {
DropColumn { name: Ident },
/// `RENAME <new_table_name>`
RenameTable { new_table_name: Ident },
/// `MODIFY COLUMN <column_name> <column_type>`
ModifyDataType { column: Column },
}
24 changes: 24 additions & 0 deletions tests-fuzz/src/translator/mysql/alter_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ impl DslTranslator<AlterTableExpr, String> for AlterTableExprTranslator {
AlterTableOperation::RenameTable { new_table_name } => {
Self::format_rename(&input.table_name, new_table_name)
}
AlterTableOperation::ModifyDataType { column } => {
Self::format_modify_data_type(&input.table_name, column)
}
})
}
}
Expand Down Expand Up @@ -72,6 +75,13 @@ impl AlterTableExprTranslator {
)
}

fn format_modify_data_type(name: impl Display, column: &Column) -> String {
format!(
"ALTER TABLE {name} MODIFY COLUMN {};",
Self::format_column(column)
)
}

fn format_location(location: &Option<AddColumnLocation>) -> Option<String> {
location.as_ref().map(|location| match location {
AddColumnLocation::First => "FIRST".to_string(),
Expand Down Expand Up @@ -155,5 +165,19 @@ mod tests {

let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
assert_eq!("ALTER TABLE test DROP COLUMN foo;", output);

let alter_expr = AlterTableExpr {
table_name: "test".into(),
alter_options: AlterTableOperation::ModifyDataType {
column: Column {
name: "host".into(),
column_type: ConcreteDataType::string_datatype(),
options: vec![],
},
},
};

let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
assert_eq!("ALTER TABLE test MODIFY COLUMN host STRING;", output);
}
}
25 changes: 25 additions & 0 deletions tests-fuzz/src/translator/postgres/alter_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ impl DslTranslator<AlterTableExpr, String> for AlterTableExprTranslator {
AlterTableOperation::RenameTable { new_table_name } => {
Self::format_rename(&input.table_name, new_table_name)
}
AlterTableOperation::ModifyDataType { column } => {
Self::format_modify_data_type(&input.table_name, column)
}
})
}
}
Expand Down Expand Up @@ -65,6 +68,13 @@ impl AlterTableExprTranslator {
)
}

fn format_modify_data_type(name: impl Display, column: &Column) -> String {
format!(
"ALTER TABLE {name} MODIFY COLUMN {};",
Self::format_column(column)
)
}

fn format_column(column: &Column) -> String {
vec![
column.name.to_string(),
Expand Down Expand Up @@ -150,5 +160,20 @@ mod tests {

let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
assert_eq!("ALTER TABLE test DROP COLUMN foo;", output);

let alter_expr = AlterTableExpr {
table_name: "test".into(),
alter_options: AlterTableOperation::ModifyDataType {
column: Column {
name: "host".into(),
column_type: ConcreteDataType::string_datatype(),
options: vec![],
},
},
};

let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
// Ignores the location and primary key option.
assert_eq!("ALTER TABLE test MODIFY COLUMN host STRING;", output);
}
}
48 changes: 31 additions & 17 deletions tests-fuzz/targets/fuzz_alter_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use rand::{Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use snafu::ResultExt;
use sqlx::{MySql, Pool};
use strum::{EnumIter, IntoEnumIterator};
use tests_fuzz::context::{TableContext, TableContextRef};
use tests_fuzz::error::{self, Result};
use tests_fuzz::fake::{
Expand All @@ -31,17 +32,16 @@ use tests_fuzz::fake::{
};
use tests_fuzz::generator::alter_expr::{
AlterExprAddColumnGeneratorBuilder, AlterExprDropColumnGeneratorBuilder,
AlterExprRenameGeneratorBuilder,
AlterExprModifyDataTypeGeneratorBuilder, AlterExprRenameGeneratorBuilder,
};
use tests_fuzz::generator::create_expr::CreateTableExprGeneratorBuilder;
use tests_fuzz::generator::Generator;
use tests_fuzz::ir::{droppable_columns, AlterTableExpr, CreateTableExpr};
use tests_fuzz::ir::{droppable_columns, modifiable_columns, AlterTableExpr, CreateTableExpr};
use tests_fuzz::translator::mysql::alter_expr::AlterTableExprTranslator;
use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator;
use tests_fuzz::translator::DslTranslator;
use tests_fuzz::utils::{init_greptime_connections_via_env, Connections};
use tests_fuzz::validator;

struct FuzzContext {
greptime: Pool<MySql>,
}
Expand All @@ -58,6 +58,14 @@ struct FuzzInput {
actions: usize,
}

#[derive(Debug, EnumIter)]
enum AlterTableOption {
AddColumn,
DropColumn,
RenameTable,
ModifyDataType,
}

fn generate_create_table_expr<R: Rng + 'static>(rng: &mut R) -> Result<CreateTableExpr> {
let columns = rng.gen_range(2..30);
let create_table_generator = CreateTableExprGeneratorBuilder::default()
Expand All @@ -76,26 +84,32 @@ fn generate_alter_table_expr<R: Rng + 'static>(
table_ctx: TableContextRef,
rng: &mut R,
) -> Result<AlterTableExpr> {
let rename = rng.gen_bool(0.2);
if rename {
let expr_generator = AlterExprRenameGeneratorBuilder::default()
let options = AlterTableOption::iter().collect::<Vec<_>>();
match options[rng.gen_range(0..options.len())] {
AlterTableOption::DropColumn if !droppable_columns(&table_ctx.columns).is_empty() => {
AlterExprDropColumnGeneratorBuilder::default()
.table_ctx(table_ctx)
.build()
.unwrap()
.generate(rng)
}
AlterTableOption::ModifyDataType if !modifiable_columns(&table_ctx.columns).is_empty() => {
AlterExprModifyDataTypeGeneratorBuilder::default()
.table_ctx(table_ctx)
.build()
.unwrap()
.generate(rng)
}
AlterTableOption::RenameTable => AlterExprRenameGeneratorBuilder::default()
.table_ctx(table_ctx)
.name_generator(Box::new(MappedGenerator::new(
WordGenerator,
merge_two_word_map_fn(random_capitalize_map, uppercase_and_keyword_backtick_map),
)))
.build()
.unwrap();
expr_generator.generate(rng)
} else {
let drop_column = rng.gen_bool(0.5) && !droppable_columns(&table_ctx.columns).is_empty();
if drop_column {
let expr_generator = AlterExprDropColumnGeneratorBuilder::default()
.table_ctx(table_ctx)
.build()
.unwrap();
expr_generator.generate(rng)
} else {
.unwrap()
.generate(rng),
_ => {
let location = rng.gen_bool(0.5);
let expr_generator = AlterExprAddColumnGeneratorBuilder::default()
.table_ctx(table_ctx)
Expand Down

0 comments on commit dd06e10

Please sign in to comment.