2019-04-12 17:48:57 -04:00
|
|
|
use serde::Deserialize;
|
2019-04-11 16:41:24 -04:00
|
|
|
use std::fmt::{self, Display, Formatter};
|
2020-02-14 09:25:03 -05:00
|
|
|
use std::fs::{self, remove_file, File};
|
2024-03-24 13:34:46 -04:00
|
|
|
use std::io::{self, BufRead, BufReader};
|
2019-05-22 07:50:23 -04:00
|
|
|
use std::path::PathBuf;
|
2024-03-27 10:00:57 -04:00
|
|
|
use std::process::{self, exit, Command, Stdio};
|
2024-03-24 13:34:46 -04:00
|
|
|
use std::{array, env, mem};
|
2024-03-24 17:22:55 -04:00
|
|
|
use winnow::ascii::{space0, Caseless};
|
2024-03-24 14:18:19 -04:00
|
|
|
use winnow::combinator::opt;
|
|
|
|
use winnow::Parser;
|
2019-04-11 16:41:24 -04:00
|
|
|
|
|
|
|
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
|
2023-02-05 01:10:23 -05:00
|
|
|
const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"];
|
2023-09-27 16:02:14 -04:00
|
|
|
const RUSTC_NO_DEBUG_ARGS: &[&str] = &["-C", "strip=debuginfo"];
|
2019-11-11 07:38:24 -05:00
|
|
|
const CONTEXT: usize = 2;
|
2024-03-28 16:10:31 -04:00
|
|
|
const CLIPPY_CARGO_TOML_PATH: &str = "exercises/22_clippy/Cargo.toml";
|
2019-04-11 16:41:24 -04:00
|
|
|
|
2024-03-25 21:14:25 -04:00
|
|
|
// Checks if the line contains the "I AM NOT DONE" comment.
|
|
|
|
fn contains_not_done_comment(input: &str) -> bool {
|
2024-03-24 14:18:19 -04:00
|
|
|
(
|
|
|
|
space0::<_, ()>,
|
|
|
|
"//",
|
|
|
|
opt('/'),
|
|
|
|
space0,
|
2024-03-24 17:22:55 -04:00
|
|
|
Caseless("I AM NOT DONE"),
|
2024-03-24 14:18:19 -04:00
|
|
|
)
|
|
|
|
.parse_next(&mut &*input)
|
|
|
|
.is_ok()
|
|
|
|
}
|
|
|
|
|
2020-10-30 09:39:28 -04:00
|
|
|
// Get a temporary file name that is hopefully unique
|
2020-06-04 10:31:17 -04:00
|
|
|
#[inline]
|
2019-04-11 16:41:24 -04:00
|
|
|
fn temp_file() -> String {
|
2020-10-30 09:39:28 -04:00
|
|
|
let thread_id: String = format!("{:?}", std::thread::current().id())
|
|
|
|
.chars()
|
|
|
|
.filter(|c| c.is_alphanumeric())
|
|
|
|
.collect();
|
|
|
|
|
2024-03-28 17:11:16 -04:00
|
|
|
format!("./temp_{}_{thread_id}", process::id())
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
// The mode of the exercise.
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 06:46:49 -04:00
|
|
|
#[derive(Deserialize, Copy, Clone, Debug)]
|
2019-04-11 16:41:24 -04:00
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
pub enum Mode {
|
2020-06-04 10:31:17 -04:00
|
|
|
// Indicates that the exercise should be compiled as a binary
|
2019-04-11 16:41:24 -04:00
|
|
|
Compile,
|
2020-06-04 10:31:17 -04:00
|
|
|
// Indicates that the exercise should be compiled as a test harness
|
2019-04-11 16:41:24 -04:00
|
|
|
Test,
|
2020-06-04 10:31:17 -04:00
|
|
|
// Indicates that the exercise should be linted with clippy
|
2020-02-14 09:25:03 -05:00
|
|
|
Clippy,
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct ExerciseList {
|
|
|
|
pub exercises: Vec<Exercise>,
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
// A representation of a rustlings exercise.
|
|
|
|
// This is deserialized from the accompanying info.toml file
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 06:46:49 -04:00
|
|
|
#[derive(Deserialize, Debug)]
|
2019-04-11 16:41:24 -04:00
|
|
|
pub struct Exercise {
|
2020-06-04 10:31:17 -04:00
|
|
|
// Name of the exercise
|
2019-11-11 09:46:32 -05:00
|
|
|
pub name: String,
|
2020-06-04 10:31:17 -04:00
|
|
|
// The path to the file containing the exercise's source code
|
2019-04-11 16:41:24 -04:00
|
|
|
pub path: PathBuf,
|
2020-06-04 10:31:17 -04:00
|
|
|
// The mode of the exercise (Test, Compile, or Clippy)
|
2019-04-11 16:41:24 -04:00
|
|
|
pub mode: Mode,
|
2020-06-04 10:31:17 -04:00
|
|
|
// The hint text associated with the exercise
|
2019-11-11 10:51:38 -05:00
|
|
|
pub hint: String,
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
// An enum to track of the state of an Exercise.
|
|
|
|
// An Exercise can be either Done or Pending
|
2024-03-26 12:47:33 -04:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2019-11-11 07:38:24 -05:00
|
|
|
pub enum State {
|
2020-06-04 10:31:17 -04:00
|
|
|
// The state of the exercise once it's been completed
|
2019-11-11 07:38:24 -05:00
|
|
|
Done,
|
2020-06-04 10:31:17 -04:00
|
|
|
// The state of the exercise while it's not completed yet
|
2019-11-11 07:38:24 -05:00
|
|
|
Pending(Vec<ContextLine>),
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
// The context information of a pending exercise
|
2024-03-26 12:47:33 -04:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2019-11-11 07:38:24 -05:00
|
|
|
pub struct ContextLine {
|
2020-06-04 10:31:17 -04:00
|
|
|
// The source code that is still pending completion
|
2019-11-11 07:38:24 -05:00
|
|
|
pub line: String,
|
2020-06-04 10:31:17 -04:00
|
|
|
// The line number of the source code still pending completion
|
2019-11-11 07:38:24 -05:00
|
|
|
pub number: usize,
|
2020-06-04 10:31:17 -04:00
|
|
|
// Whether or not this is important
|
2019-11-11 07:38:24 -05:00
|
|
|
pub important: bool,
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
// The result of compiling an exercise
|
2020-02-20 14:11:53 -05:00
|
|
|
pub struct CompiledExercise<'a> {
|
|
|
|
exercise: &'a Exercise,
|
|
|
|
_handle: FileHandle,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> CompiledExercise<'a> {
|
2020-06-04 10:31:17 -04:00
|
|
|
// Run the compiled exercise
|
2020-02-20 14:11:53 -05:00
|
|
|
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
|
|
|
self.exercise.run()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
// A representation of an already executed binary
|
2020-02-20 14:11:53 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ExerciseOutput {
|
2020-06-04 10:31:17 -04:00
|
|
|
// The textual contents of the standard output of the binary
|
2020-02-20 14:11:53 -05:00
|
|
|
pub stdout: String,
|
2020-06-04 10:31:17 -04:00
|
|
|
// The textual contents of the standard error of the binary
|
2020-02-20 14:11:53 -05:00
|
|
|
pub stderr: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct FileHandle;
|
|
|
|
|
|
|
|
impl Drop for FileHandle {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
clean();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 16:41:24 -04:00
|
|
|
impl Exercise {
|
2020-02-20 14:11:53 -05:00
|
|
|
pub fn compile(&self) -> Result<CompiledExercise, ExerciseOutput> {
|
|
|
|
let cmd = match self.mode {
|
2019-04-11 16:41:24 -04:00
|
|
|
Mode::Compile => Command::new("rustc")
|
2023-08-26 17:07:20 -04:00
|
|
|
.args([self.path.to_str().unwrap(), "-o", &temp_file()])
|
2019-04-11 16:41:24 -04:00
|
|
|
.args(RUSTC_COLOR_ARGS)
|
2023-02-05 01:10:23 -05:00
|
|
|
.args(RUSTC_EDITION_ARGS)
|
2023-09-27 16:02:14 -04:00
|
|
|
.args(RUSTC_NO_DEBUG_ARGS)
|
2019-04-11 16:41:24 -04:00
|
|
|
.output(),
|
|
|
|
Mode::Test => Command::new("rustc")
|
2023-08-26 17:07:20 -04:00
|
|
|
.args(["--test", self.path.to_str().unwrap(), "-o", &temp_file()])
|
2019-04-11 16:41:24 -04:00
|
|
|
.args(RUSTC_COLOR_ARGS)
|
2023-02-05 01:10:23 -05:00
|
|
|
.args(RUSTC_EDITION_ARGS)
|
2023-09-27 16:02:14 -04:00
|
|
|
.args(RUSTC_NO_DEBUG_ARGS)
|
2019-04-11 16:41:24 -04:00
|
|
|
.output(),
|
2020-02-14 09:25:03 -05:00
|
|
|
Mode::Clippy => {
|
|
|
|
let cargo_toml = format!(
|
|
|
|
r#"[package]
|
|
|
|
name = "{}"
|
|
|
|
version = "0.0.1"
|
2022-07-01 10:49:36 -04:00
|
|
|
edition = "2021"
|
2020-02-14 09:25:03 -05:00
|
|
|
[[bin]]
|
|
|
|
name = "{}"
|
|
|
|
path = "{}.rs""#,
|
|
|
|
self.name, self.name, self.name
|
|
|
|
);
|
2021-03-20 14:52:57 -04:00
|
|
|
let cargo_toml_error_msg = if env::var("NO_EMOJI").is_ok() {
|
|
|
|
"Failed to write Clippy Cargo.toml file."
|
|
|
|
} else {
|
|
|
|
"Failed to write 📎 Clippy 📎 Cargo.toml file."
|
2021-03-19 05:16:07 -04:00
|
|
|
};
|
2021-04-18 09:40:47 -04:00
|
|
|
fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg);
|
2021-09-21 05:50:15 -04:00
|
|
|
// To support the ability to run the clippy exercises, build
|
2020-04-08 00:30:29 -04:00
|
|
|
// an executable, in addition to running clippy. With a
|
|
|
|
// compilation failure, this would silently fail. But we expect
|
|
|
|
// clippy to reflect the same failure while compiling later.
|
|
|
|
Command::new("rustc")
|
2023-08-26 17:07:20 -04:00
|
|
|
.args([self.path.to_str().unwrap(), "-o", &temp_file()])
|
2020-04-08 00:30:29 -04:00
|
|
|
.args(RUSTC_COLOR_ARGS)
|
2023-02-05 01:10:23 -05:00
|
|
|
.args(RUSTC_EDITION_ARGS)
|
2023-09-27 16:02:14 -04:00
|
|
|
.args(RUSTC_NO_DEBUG_ARGS)
|
2024-03-25 12:21:54 -04:00
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
.status()
|
2020-04-08 00:30:29 -04:00
|
|
|
.expect("Failed to compile!");
|
2020-02-14 09:25:03 -05:00
|
|
|
// Due to an issue with Clippy, a cargo clean is required to catch all lints.
|
|
|
|
// See https://github.com/rust-lang/rust-clippy/issues/2604
|
2020-06-14 08:48:51 -04:00
|
|
|
// This is already fixed on Clippy's master branch. See this issue to track merging into Cargo:
|
2020-02-14 09:25:03 -05:00
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/3837
|
|
|
|
Command::new("cargo")
|
2023-08-26 17:07:20 -04:00
|
|
|
.args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
|
2020-02-14 09:25:03 -05:00
|
|
|
.args(RUSTC_COLOR_ARGS)
|
2024-03-25 12:21:54 -04:00
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
.status()
|
2020-02-14 09:25:03 -05:00
|
|
|
.expect("Failed to run 'cargo clean'");
|
|
|
|
Command::new("cargo")
|
2023-08-26 17:07:20 -04:00
|
|
|
.args(["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
|
2020-02-14 09:25:03 -05:00
|
|
|
.args(RUSTC_COLOR_ARGS)
|
2023-08-26 17:07:20 -04:00
|
|
|
.args(["--", "-D", "warnings", "-D", "clippy::float_cmp"])
|
2020-02-14 09:25:03 -05:00
|
|
|
.output()
|
|
|
|
}
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
2020-02-20 14:11:53 -05:00
|
|
|
.expect("Failed to run 'compile' command.");
|
|
|
|
|
|
|
|
if cmd.status.success() {
|
|
|
|
Ok(CompiledExercise {
|
2021-08-24 08:06:30 -04:00
|
|
|
exercise: self,
|
2020-02-20 14:11:53 -05:00
|
|
|
_handle: FileHandle,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
clean();
|
|
|
|
Err(ExerciseOutput {
|
|
|
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
|
|
|
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
|
|
|
})
|
|
|
|
}
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
|
|
|
|
2020-02-20 14:11:53 -05:00
|
|
|
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
2020-06-04 10:31:17 -04:00
|
|
|
let arg = match self.mode {
|
|
|
|
Mode::Test => "--show-output",
|
2020-08-10 10:42:54 -04:00
|
|
|
_ => "",
|
2020-06-04 10:31:17 -04:00
|
|
|
};
|
2023-08-26 17:07:20 -04:00
|
|
|
let cmd = Command::new(temp_file())
|
2020-08-10 10:42:54 -04:00
|
|
|
.arg(arg)
|
2019-04-11 16:41:24 -04:00
|
|
|
.output()
|
2020-02-20 14:11:53 -05:00
|
|
|
.expect("Failed to run 'run' command");
|
|
|
|
|
|
|
|
let output = ExerciseOutput {
|
|
|
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
|
|
|
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
|
|
|
};
|
2019-04-11 16:41:24 -04:00
|
|
|
|
2020-02-20 14:11:53 -05:00
|
|
|
if cmd.status.success() {
|
|
|
|
Ok(output)
|
|
|
|
} else {
|
|
|
|
Err(output)
|
|
|
|
}
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
2019-11-11 07:38:24 -05:00
|
|
|
|
|
|
|
pub fn state(&self) -> State {
|
2024-03-24 13:34:46 -04:00
|
|
|
let source_file = File::open(&self.path).unwrap_or_else(|e| {
|
2024-03-25 21:14:25 -04:00
|
|
|
println!(
|
|
|
|
"Failed to open the exercise file {}: {e}",
|
|
|
|
self.path.display(),
|
|
|
|
);
|
|
|
|
exit(1);
|
2023-09-25 03:36:43 -04:00
|
|
|
});
|
2024-03-24 13:34:46 -04:00
|
|
|
let mut source_reader = BufReader::new(source_file);
|
2024-03-25 21:14:25 -04:00
|
|
|
|
|
|
|
// Read the next line into `buf` without the newline at the end.
|
2024-03-24 13:34:46 -04:00
|
|
|
let mut read_line = |buf: &mut String| -> io::Result<_> {
|
|
|
|
let n = source_reader.read_line(buf)?;
|
|
|
|
if buf.ends_with('\n') {
|
|
|
|
buf.pop();
|
|
|
|
if buf.ends_with('\r') {
|
|
|
|
buf.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(n)
|
2019-11-11 07:38:24 -05:00
|
|
|
};
|
|
|
|
|
2024-03-25 21:14:25 -04:00
|
|
|
let mut current_line_number: usize = 1;
|
2024-03-25 21:26:26 -04:00
|
|
|
// Keep the last `CONTEXT` lines while iterating over the file lines.
|
2024-03-24 13:34:46 -04:00
|
|
|
let mut prev_lines: [_; CONTEXT] = array::from_fn(|_| String::with_capacity(256));
|
|
|
|
let mut line = String::with_capacity(256);
|
2019-11-11 07:38:24 -05:00
|
|
|
|
2024-03-24 13:34:46 -04:00
|
|
|
loop {
|
2024-03-25 21:14:25 -04:00
|
|
|
let n = read_line(&mut line).unwrap_or_else(|e| {
|
|
|
|
println!(
|
|
|
|
"Failed to read the exercise file {}: {e}",
|
|
|
|
self.path.display(),
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Reached the end of the file and didn't find the comment.
|
|
|
|
if n == 0 {
|
|
|
|
return State::Done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if contains_not_done_comment(&line) {
|
|
|
|
let mut context = Vec::with_capacity(2 * CONTEXT + 1);
|
2024-03-25 21:26:26 -04:00
|
|
|
// Previous lines.
|
2024-03-25 21:14:25 -04:00
|
|
|
for (ind, prev_line) in prev_lines
|
|
|
|
.into_iter()
|
|
|
|
.take(current_line_number - 1)
|
|
|
|
.enumerate()
|
|
|
|
.rev()
|
|
|
|
{
|
|
|
|
context.push(ContextLine {
|
|
|
|
line: prev_line,
|
|
|
|
number: current_line_number - 1 - ind,
|
|
|
|
important: false,
|
|
|
|
});
|
|
|
|
}
|
2024-03-24 13:34:46 -04:00
|
|
|
|
2024-03-25 21:26:26 -04:00
|
|
|
// Current line.
|
2024-03-25 21:14:25 -04:00
|
|
|
context.push(ContextLine {
|
|
|
|
line,
|
|
|
|
number: current_line_number,
|
|
|
|
important: true,
|
|
|
|
});
|
|
|
|
|
2024-03-25 21:26:26 -04:00
|
|
|
// Next lines.
|
2024-03-25 21:14:25 -04:00
|
|
|
for ind in 0..CONTEXT {
|
|
|
|
let mut next_line = String::with_capacity(256);
|
|
|
|
let Ok(n) = read_line(&mut next_line) else {
|
2024-03-25 21:26:26 -04:00
|
|
|
// If an error occurs, just ignore the next lines.
|
2024-03-25 21:14:25 -04:00
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
2024-03-25 21:26:26 -04:00
|
|
|
// Reached the end of the file.
|
2024-03-25 21:14:25 -04:00
|
|
|
if n == 0 {
|
|
|
|
break;
|
2024-03-24 13:34:46 -04:00
|
|
|
}
|
2024-03-25 21:14:25 -04:00
|
|
|
|
|
|
|
context.push(ContextLine {
|
|
|
|
line: next_line,
|
|
|
|
number: current_line_number + 1 + ind,
|
|
|
|
important: false,
|
|
|
|
});
|
2024-03-24 13:34:46 -04:00
|
|
|
}
|
2024-03-25 21:14:25 -04:00
|
|
|
|
|
|
|
return State::Pending(context);
|
2024-03-24 13:34:46 -04:00
|
|
|
}
|
2019-11-11 07:38:24 -05:00
|
|
|
|
2024-03-25 21:14:25 -04:00
|
|
|
current_line_number += 1;
|
2024-03-25 21:26:26 -04:00
|
|
|
// Add the current line as a previous line and shift the older lines by one.
|
2024-03-25 21:14:25 -04:00
|
|
|
for prev_line in &mut prev_lines {
|
|
|
|
mem::swap(&mut line, prev_line);
|
|
|
|
}
|
2024-03-25 21:26:26 -04:00
|
|
|
// The current line now contains the oldest previous line.
|
|
|
|
// Recycle it for reading the next line.
|
2024-03-25 21:14:25 -04:00
|
|
|
line.clear();
|
|
|
|
}
|
2019-11-11 07:38:24 -05:00
|
|
|
}
|
2020-12-12 13:45:37 -05:00
|
|
|
|
|
|
|
// Check that the exercise looks to be solved using self.state()
|
|
|
|
// This is not the best way to check since
|
2021-03-12 09:26:57 -05:00
|
|
|
// the user can just remove the "I AM NOT DONE" string from the file
|
2020-12-12 13:45:37 -05:00
|
|
|
// without actually having solved anything.
|
|
|
|
// The only other way to truly check this would to compile and run
|
|
|
|
// the exercise; which would be both costly and counterintuitive
|
|
|
|
pub fn looks_done(&self) -> bool {
|
|
|
|
self.state() == State::Done
|
|
|
|
}
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Exercise {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
|
|
write!(f, "{}", self.path.to_str().unwrap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 10:31:17 -04:00
|
|
|
#[inline]
|
2020-02-20 14:11:53 -05:00
|
|
|
fn clean() {
|
2023-08-26 17:07:20 -04:00
|
|
|
let _ignored = remove_file(temp_file());
|
2020-02-20 14:11:53 -05:00
|
|
|
}
|
|
|
|
|
2019-04-12 17:48:57 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2019-05-22 07:50:23 -04:00
|
|
|
use std::path::Path;
|
2019-04-12 17:48:57 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_clean() {
|
2023-08-26 17:07:20 -04:00
|
|
|
File::create(temp_file()).unwrap();
|
2019-04-12 17:48:57 -04:00
|
|
|
let exercise = Exercise {
|
2019-11-11 09:46:32 -05:00
|
|
|
name: String::from("example"),
|
2020-02-20 14:11:53 -05:00
|
|
|
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
|
|
|
|
mode: Mode::Compile,
|
2019-11-11 10:51:38 -05:00
|
|
|
hint: String::from(""),
|
2019-04-12 17:48:57 -04:00
|
|
|
};
|
2020-02-20 14:11:53 -05:00
|
|
|
let compiled = exercise.compile().unwrap();
|
|
|
|
drop(compiled);
|
2019-04-12 17:48:57 -04:00
|
|
|
assert!(!Path::new(&temp_file()).exists());
|
|
|
|
}
|
2019-11-11 07:38:24 -05:00
|
|
|
|
2023-09-27 16:02:14 -04:00
|
|
|
#[test]
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
fn test_no_pdb_file() {
|
|
|
|
[Mode::Compile, Mode::Test] // Clippy doesn't like to test
|
|
|
|
.iter()
|
|
|
|
.for_each(|mode| {
|
|
|
|
let exercise = Exercise {
|
|
|
|
name: String::from("example"),
|
|
|
|
// We want a file that does actually compile
|
|
|
|
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
|
|
|
|
mode: *mode,
|
|
|
|
hint: String::from(""),
|
|
|
|
};
|
|
|
|
let _ = exercise.compile().unwrap();
|
|
|
|
assert!(!Path::new(&format!("{}.pdb", temp_file())).exists());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-11-11 07:38:24 -05:00
|
|
|
#[test]
|
|
|
|
fn test_pending_state() {
|
|
|
|
let exercise = Exercise {
|
2019-11-11 11:28:19 -05:00
|
|
|
name: "pending_exercise".into(),
|
2019-11-11 07:38:24 -05:00
|
|
|
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
|
|
|
|
mode: Mode::Compile,
|
2019-11-11 11:28:19 -05:00
|
|
|
hint: String::new(),
|
2019-11-11 07:38:24 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
let state = exercise.state();
|
|
|
|
let expected = vec![
|
|
|
|
ContextLine {
|
|
|
|
line: "// fake_exercise".to_string(),
|
|
|
|
number: 1,
|
|
|
|
important: false,
|
|
|
|
},
|
|
|
|
ContextLine {
|
|
|
|
line: "".to_string(),
|
|
|
|
number: 2,
|
|
|
|
important: false,
|
|
|
|
},
|
|
|
|
ContextLine {
|
|
|
|
line: "// I AM NOT DONE".to_string(),
|
|
|
|
number: 3,
|
|
|
|
important: true,
|
|
|
|
},
|
|
|
|
ContextLine {
|
|
|
|
line: "".to_string(),
|
|
|
|
number: 4,
|
|
|
|
important: false,
|
|
|
|
},
|
|
|
|
ContextLine {
|
|
|
|
line: "fn main() {".to_string(),
|
|
|
|
number: 5,
|
|
|
|
important: false,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(state, State::Pending(expected));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_finished_exercise() {
|
|
|
|
let exercise = Exercise {
|
2019-11-11 11:28:19 -05:00
|
|
|
name: "finished_exercise".into(),
|
2019-11-11 07:38:24 -05:00
|
|
|
path: PathBuf::from("tests/fixture/state/finished_exercise.rs"),
|
|
|
|
mode: Mode::Compile,
|
2019-11-11 11:28:19 -05:00
|
|
|
hint: String::new(),
|
2019-11-11 07:38:24 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(exercise.state(), State::Done);
|
|
|
|
}
|
2020-06-04 10:31:17 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_exercise_with_output() {
|
|
|
|
let exercise = Exercise {
|
2020-09-27 15:57:51 -04:00
|
|
|
name: "exercise_with_output".into(),
|
2020-06-04 10:31:17 -04:00
|
|
|
path: PathBuf::from("tests/fixture/success/testSuccess.rs"),
|
|
|
|
mode: Mode::Test,
|
|
|
|
hint: String::new(),
|
|
|
|
};
|
|
|
|
let out = exercise.compile().unwrap().run().unwrap();
|
|
|
|
assert!(out.stdout.contains("THIS TEST TOO SHALL PASS"));
|
|
|
|
}
|
2024-03-24 14:18:19 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_not_done() {
|
2024-03-25 21:14:25 -04:00
|
|
|
assert!(contains_not_done_comment("// I AM NOT DONE"));
|
|
|
|
assert!(contains_not_done_comment("/// I AM NOT DONE"));
|
|
|
|
assert!(contains_not_done_comment("// I AM NOT DONE"));
|
|
|
|
assert!(contains_not_done_comment("/// I AM NOT DONE"));
|
|
|
|
assert!(contains_not_done_comment("// I AM NOT DONE "));
|
|
|
|
assert!(contains_not_done_comment("// I AM NOT DONE!"));
|
|
|
|
assert!(contains_not_done_comment("// I am not done"));
|
|
|
|
assert!(contains_not_done_comment("// i am NOT done"));
|
|
|
|
|
|
|
|
assert!(!contains_not_done_comment("I AM NOT DONE"));
|
|
|
|
assert!(!contains_not_done_comment("// NOT DONE"));
|
|
|
|
assert!(!contains_not_done_comment("DONE"));
|
2024-03-24 14:18:19 -04:00
|
|
|
}
|
2019-04-11 16:41:24 -04:00
|
|
|
}
|