Skip to content
Merged
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
155 changes: 86 additions & 69 deletions crates/cli/src/cli/problem.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::fs;

use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};

use crate::config::Settings;
use crate::problem::{
archive, check, compare, create, generate,
run::{RunnableCategory, RunnableFile},
solve, test,
};
use crate::problem::fuzz;
use crate::problem::run::{RunnableCategory, RunnableFile};
use crate::problem::{archive, check, compare, create, generate, solve, test};
use crate::util::{get_problem_from_cwd, get_project_root};

pub fn cli() -> Command {
Expand Down Expand Up @@ -38,36 +36,12 @@ pub fn cli() -> Command {
)
.subcommand(
Command::new("compare")
.about("Compare two solutions")
.about("Compare two or more solutions")
.args([
Arg::new("generate")
.long("generate")
.help("Generate new test cases until the solutions produce different results")
.action(ArgAction::SetTrue),
Arg::new("file1")
.long("file1")
.help("Name of the first solution file")
.action(ArgAction::Set),
Arg::new("lang1")
.long("lang1")
.help("Language of the first solution file (e.g. cpp, py)")
.action(ArgAction::Set),
Arg::new("file2")
.long("file2")
.help("Name of the second solution file")
.action(ArgAction::Set),
Arg::new("lang2")
.long("lang2")
.help("Language of the second solution file (e.g. cpp, py)")
.action(ArgAction::Set),
Arg::new("generator-file")
.long("generator-file")
.help("Name of the generator file")
.action(ArgAction::Set),
Arg::new("generator-lang")
.long("generator-lang")
.help("Language of the generator file (e.g. cpp, py)")
.action(ArgAction::Set),
Arg::new("file")
.long("file")
.help("Name of the solution file")
.action(ArgAction::Append),
Arg::new("problem")
.short('p')
.long("problem")
Expand All @@ -93,6 +67,25 @@ pub fn cli() -> Command {
.required(true),
),
)
.subcommand(
Command::new("fuzz")
.about("Find potential edge cases in two or more solutions")
.args([
Arg::new("file")
.long("file")
.help("Name of the solution file")
.action(ArgAction::Append),
Arg::new("generator-file")
.long("generator-file")
.help("Name of the generator file")
.action(ArgAction::Set),
Arg::new("problem")
.short('p')
.long("problem")
.help("Problem name (this is not the problem title)")
.action(ArgAction::Set),
]),
)
.subcommand(
Command::new("generate")
.about("Generate a test case input with a generator file")
Expand All @@ -101,10 +94,6 @@ pub fn cli() -> Command {
.long("file")
.help("Name of the generator file")
.action(ArgAction::Set),
Arg::new("lang")
.long("lang")
.help("Language of the generator file (e.g. cpp, py)")
.action(ArgAction::Set),
Arg::new("problem")
.short('p')
.long("problem")
Expand Down Expand Up @@ -187,38 +176,30 @@ pub fn exec(args: &ArgMatches, settings: &Settings) -> Result<()> {
Some(name) => name,
None => &get_problem_from_cwd(&problems_dir)?,
};
let generate = (cmd.try_get_one::<bool>("generate")?).unwrap_or(&false);

let solution_1 = RunnableFile::new(
settings,
RunnableCategory::Solution,
cmd.try_get_one::<String>("file1")?,
cmd.try_get_one::<String>("lang1")?,
);
let files: Vec<&String> = cmd
.try_get_many::<String>("file")?
.context("At least two solution files are required for comparison")?
.collect();

let solution_2 = RunnableFile::new(
settings,
RunnableCategory::Solution,
cmd.try_get_one::<String>("file2")?,
cmd.try_get_one::<String>("lang2")?,
);
if files.len() < 2 {
bail!("At least two solution files are required for comparison");
}

let generator = RunnableFile::new(
settings,
RunnableCategory::Generator,
cmd.try_get_one::<String>("generator-file")?,
cmd.try_get_one::<String>("generator-lang")?,
);
let mut solution_files: Vec<RunnableFile> = Vec::new();
for f in files {
let solution_file =
RunnableFile::new(settings, RunnableCategory::Solution, Some(f));
solution_files.push(solution_file?);
}

compare::compare(
settings,
&problems_dir,
problem_name,
generate,
&solution_1,
&solution_2,
&generator,
)?;
let compare_args = compare::CompareArgs {
problems_dir: &problems_dir,
problem_name: problem_name.to_string(),
solution_files,
};

compare::compare(settings, &compare_args)?;
}
Some(("create", cmd)) => {
let problem_name = cmd
Expand All @@ -231,6 +212,43 @@ pub fn exec(args: &ArgMatches, settings: &Settings) -> Result<()> {

create::create(&problems_dir, problem_name, *difficulty)?;
}
Some(("fuzz", cmd)) => {
let problem_name = match cmd.try_get_one::<String>("problem")? {
Some(name) => name,
None => &get_problem_from_cwd(&problems_dir)?,
};

let files: Vec<&String> = cmd
.try_get_many::<String>("file")?
.context("At least two solution files are required for fuzzing")?
.collect();

if files.len() < 2 {
bail!("At least two solution files are required for fuzzing");
}

let mut solution_files: Vec<RunnableFile> = Vec::new();
for f in files {
let solution_file =
RunnableFile::new(settings, RunnableCategory::Solution, Some(f));
solution_files.push(solution_file?);
}

let generator = RunnableFile::new(
settings,
RunnableCategory::Generator,
cmd.try_get_one::<String>("generator-file")?,
)?;

let fuzz_args = fuzz::FuzzArgs {
problems_dir: &problems_dir,
problem_name: problem_name.to_string(),
solution_files,
generator,
};

fuzz::fuzz(settings, &fuzz_args)?;
}
Some(("generate", cmd)) => {
let problem_name = match cmd.try_get_one::<String>("problem")? {
Some(name) => name,
Expand All @@ -241,8 +259,7 @@ pub fn exec(args: &ArgMatches, settings: &Settings) -> Result<()> {
settings,
RunnableCategory::Generator,
cmd.try_get_one::<String>("file")?,
cmd.try_get_one::<String>("lang")?,
);
)?;

let test_name = cmd
.try_get_one::<String>("test-name")?
Expand Down
Loading