Skip to content

Commit

Permalink
Add Skipped removal outcome
Browse files Browse the repository at this point in the history
  • Loading branch information
smoelius committed Jan 10, 2024
1 parent 25b6b15 commit f6bf667
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 12 deletions.
80 changes: 71 additions & 9 deletions core/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,30 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);

static CTRLC: AtomicBool = AtomicBool::new(false);

#[derive(Clone)]
pub(crate) struct Removal {
pub span: Span,
pub text: String,
pub outcome: Outcome,
}

#[derive(Debug)]
enum MismatchKind {
Missing,
Unexpected,
}

impl Display for MismatchKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", format!("{self:?}").to_kebab_case())
}
}

struct Mismatch {
kind: MismatchKind,
removal: Removal,
}

struct Context<'a> {
opts: Necessist,
root: Rc<PathBuf>,
Expand Down Expand Up @@ -246,7 +264,8 @@ fn run(mut context: Context, test_file_span_map: BTreeMap<SourceFile, Vec<Span>>
}

if result.is_err() {
update_progress(&context, false, span_iter.count())?;
let n = skip_present_spans(&context, span_iter)?;
update_progress(&context, None, n)?;
continue;
}
}
Expand Down Expand Up @@ -275,7 +294,7 @@ fn run(mut context: Context, test_file_span_map: BTreeMap<SourceFile, Vec<Span>>
emit(&mut context, span, &text, outcome)?;
}

update_progress(&context, false, 1)?;
update_progress(&context, None, 1)?;
}
}

Expand Down Expand Up @@ -381,20 +400,23 @@ fn canonicalize_test_files(context: &LightContext) -> Result<Vec<PathBuf>> {
fn skip_past_removals<'a, I, J>(
span_iter: &mut Peekable<I>,
removal_iter: &mut Peekable<J>,
) -> (bool, usize)
) -> (Option<Mismatch>, usize)
where
I: Iterator<Item = &'a Span>,
J: Iterator<Item = Removal>,
{
let mut mismatch = false;
let mut mismatch = None;
let mut n = 0;
while let Some(&span) = span_iter.peek() {
let Some(removal) = removal_iter.peek() else {
break;
};
match span.cmp(&removal.span) {
std::cmp::Ordering::Less => {
mismatch = true;
mismatch = Some(Mismatch {
kind: MismatchKind::Unexpected,
removal: removal.clone(),
});
break;
}
std::cmp::Ordering::Equal => {
Expand All @@ -403,7 +425,12 @@ where
n += 1;
}
std::cmp::Ordering::Greater => {
mismatch = true;
if mismatch.is_none() {
mismatch = Some(Mismatch {
kind: MismatchKind::Missing,
removal: removal.clone(),
});
}
let _removal = removal_iter.next();
}
}
Expand All @@ -412,12 +439,47 @@ where
(mismatch, n)
}

fn update_progress(context: &Context, mismatch: bool, n: usize) -> Result<()> {
if mismatch {
fn skip_present_spans<'a>(
context: &Context,
span_iter: impl Iterator<Item = &'a Span>,
) -> Result<usize> {
let mut n = 0;

let sqlite = sqlite_init_lazy(&context.light())?;

for span in span_iter {
if let Some(sqlite) = sqlite.borrow_mut().as_mut() {
let text = span.source_text()?;
let removal = Removal {
span: span.clone(),
text,
outcome: Outcome::Skipped,
};
sqlite::insert(sqlite, &removal)?;
}
n += 1;
}

Ok(n)
}

fn update_progress(context: &Context, mismatch: Option<Mismatch>, n: usize) -> Result<()> {
if let Some(Mismatch {
kind,
removal: Removal { span, text, .. },
}) = mismatch
{
warn(
&context.light(),
Warning::FilesChanged,
"Configuration or test files have changed since necessist.db was created",
&format!(
"\
Configuration or test files have changed since necessist.db was created; the following entry is \
{kind}:
{}: `{}`",
span.to_console_string(),
text.replace('\r', ""),
),
WarnFlags::ONCE,
)?;
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/create_table_removal.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CREATE TABLE removal (
span TEXT NOT NULL,
text TEXT NOT NULL,
outcome TEXT NOT NULL CHECK (outcome IN ('nonbuildable', 'failed', 'timed-out', 'passed')),
outcome TEXT NOT NULL CHECK (outcome IN ('skipped', 'nonbuildable', 'failed', 'timed-out', 'passed')),
url TEXT NOT NULL,
PRIMARY KEY (span)
)
8 changes: 8 additions & 0 deletions core/src/outcome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ use std::str::FromStr;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;

/// The outcome of running a test with a statement or method call removed.
#[derive(Clone, Copy, Debug, EnumIter, PartialEq)]
pub(crate) enum Outcome {
// The test(s) were not run (e.g., because a dry run failed).
Skipped,
/// The test(s) did not build.
Nonbuildable,
/// The test(s) built but failed.
Failed,
/// The test(s) built but timed-out.
TimedOut,
// The test(s) built and passed.
Passed,
}

Expand All @@ -34,6 +41,7 @@ impl FromStr for Outcome {
impl Outcome {
pub fn style(self) -> Style {
match self {
Outcome::Skipped => Style::default().dimmed(),
Outcome::Nonbuildable => Blue.normal(),
Outcome::Failed => Green.normal(),
Outcome::TimedOut => Yellow.normal(),
Expand Down
112 changes: 110 additions & 2 deletions necessist/tests/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ use assert_cmd::prelude::*;
use fs_extra::dir::{copy, CopyOptions};
use necessist_core::util;
use predicates::prelude::*;
use std::{path::PathBuf, process::Command};
use std::{path::PathBuf, process::Command, sync::Mutex};

mod tempfile_util;
use tempfile_util::tempdir;

const ROOT: &str = "../examples/basic";
const TIMEOUT: &str = "5";

// smoelius: Two tests use the `basic` fixture, but only one can run at a time.
static BASIC_MUTEX: Mutex<()> = Mutex::new(());

#[test]
fn necessist_db_can_be_moved() {
const ROOT: &str = "../examples/basic";

let _lock = BASIC_MUTEX.lock().unwrap();

Command::cargo_bin("necessist")
.unwrap()
.args(["--root", ROOT, "--timeout", TIMEOUT])
Expand Down Expand Up @@ -41,3 +47,105 @@ fn necessist_db_can_be_moved() {
.success()
.stdout(predicate::eq("4 candidates in 1 test file\n"));
}

#[test]
fn resume_following_dry_run_failure() {
const ROOT: &str = "examples/failure";

let assert = Command::cargo_bin("necessist")
.unwrap()
.current_dir("..")
.args(["--root", ROOT])
.assert()
.success();
let stdout_normalized = std::str::from_utf8(&assert.get_output().stdout)
.unwrap()
.replace('\\', "/");
assert!(
stdout_normalized.starts_with(
"\
2 candidates in 2 test files
examples/failure/tests/a.rs: dry running
examples/failure/tests/a.rs: Warning: dry run failed: code=101
"
),
"{stdout_normalized:?}",
);

let necessist_db = PathBuf::from("..").join(ROOT).join("necessist.db");

let _remove_file = util::RemoveFile(necessist_db);

Command::cargo_bin("necessist")
.unwrap()
.current_dir("..")
.args(["--root", ROOT, "--resume"])
.assert()
.success()
.stdout(predicate::eq("2 candidates in 2 test files\n"));
}

// smoelius: Apperently, sending a ctrl-c on Windows is non-trivial:
// https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows
#[cfg(not(windows))]
#[test]
fn resume_following_ctrl_c() {
use similar_asserts::SimpleDiff;
use std::{process::Stdio, thread::sleep, time::Duration};

const ROOT: &str = "examples/basic";

fn command() -> Command {
let mut command = Command::cargo_bin("necessist").unwrap();
command
.current_dir("..")
.args(["--root", ROOT, "--timeout", TIMEOUT, "--verbose"]);
command
}

let _lock = BASIC_MUTEX.lock().unwrap();

let child = command().stderr(Stdio::piped()).spawn().unwrap();

sleep(Duration::from_secs(1));

kill().arg(child.id().to_string()).assert().success();

let output = child.wait_with_output().unwrap();

let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.ends_with("Ctrl-C detected\n"), "{stderr:?}");

let necessist_db = PathBuf::from("..").join(ROOT).join("necessist.db");

let _remove_file = util::RemoveFile(necessist_db);

let assert = command().arg("--resume").assert().success();

// smoelius: N.B. `stdout_expected` intentionally lacks the following line:
// examples/basic/src/lib.rs:4:5-4:12: `n += 1;` passed
let stdout_expected: &str = "\
4 candidates in 1 test file
examples/basic/src/lib.rs: dry running
examples/basic/src/lib.rs: mutilating
examples/basic/src/lib.rs:14:9-14:16: `n += 1;` timed-out
examples/basic/src/lib.rs:21:5-21:12: `n += 1;` failed
examples/basic/src/lib.rs:28:18-28:27: `.join(\"\")` nonbuildable
";

let stdout_actual = std::str::from_utf8(&assert.get_output().stdout).unwrap();

assert_eq!(
stdout_expected,
stdout_actual,
"{}",
SimpleDiff::from_str(stdout_expected, stdout_actual, "left", "right")
);
}

#[cfg(not(windows))]
fn kill() -> Command {
let mut command = Command::new("kill");
command.arg("-INT");
command
}

0 comments on commit f6bf667

Please sign in to comment.