Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve integration tests #549

Merged
merged 1 commit into from
Aug 2, 2024
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
16 changes: 16 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions cargo-insta/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ clap = {version = "=4.1", features = ["derive", "env"]}
[dev-dependencies]
walkdir = "2.3.1"
similar= "2.2.1"
itertools = "0.10.0"
139 changes: 84 additions & 55 deletions cargo-insta/tests/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/// Integration tests which allow creating a full repo, running `cargo-insta`
/// and then checking the output.
///
/// We can write more docs if that would be helpful. For the moment one thing to
/// be aware of: it seems the packages must have different names, or we'll see
/// interference between the tests.
///
/// (That seems to be because they all share the same `target` directory, which
/// cargo will confuse for each other if they share the same name. I haven't
/// worked out why — this is the case even if the files are the same between two
/// tests but with different commands — and those files exist in different
/// temporary workspace dirs. (We could try to enforce different names, or give
/// up using a consistent target directory for a cache, but it would slow down
/// repeatedly running the tests locally. To demonstrate the effect, name crates
/// the same...)
use std::collections::HashMap;
use std::env;
use std::fs;
Expand All @@ -6,36 +21,56 @@ use std::process::Command;

use ignore::WalkBuilder;
use insta::assert_snapshot;
use itertools::Itertools;
use similar::udiff::unified_diff;
use tempfile::TempDir;

struct TestProject {
struct TestFiles {
files: HashMap<PathBuf, String>,
/// Temporary directory where the project is created
temp_dir: TempDir,
/// Path of this repo, so we can have it as a dependency in the test project
project_path: Option<PathBuf>,
/// File tree at start of test
file_tree: Option<String>,
}

fn workspace_path() -> PathBuf {
impl TestFiles {
fn new() -> Self {
Self {
files: HashMap::new(),
}
}

fn add_file<P: AsRef<Path>>(mut self, path: P, content: String) -> Self {
self.files.insert(path.as_ref().to_path_buf(), content);
self
}

fn create_project(self) -> TestProject {
TestProject::new(self.files)
}
}

/// Path of the insta crate in this repo, which we use as a dependency in the test project
fn insta_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("insta")
.to_path_buf()
}

/// A shared `target` directory for all tests to use, to allow caching.
fn target_dir() -> PathBuf {
let target_dir = env::var("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_path().join("target"))
.unwrap_or_else(|_| insta_path().join("target"))
.join("test-projects");
fs::create_dir_all(&target_dir).unwrap();
target_dir
}

fn assert_success(output: &std::process::Output) {
// Print stderr. Cargo test hides this when tests are successful, but if a
// test successfully exectues a command but then fails (e.g. on a snapshot),
// we would otherwise lose any output from the command such as `dbg!`
// statements.
eprint!("{}", String::from_utf8_lossy(&output.stderr));
assert!(
output.status.success(),
"Tests failed: {}\n{}",
Expand All @@ -44,47 +79,38 @@ fn assert_success(output: &std::process::Output) {
);
}

impl TestProject {
fn new() -> Self {
Self {
files: HashMap::new(),
temp_dir: TempDir::new().unwrap(),
project_path: None,
file_tree: None,
}
}

fn add_file<P: AsRef<Path>>(mut self, path: P, content: String) -> Self {
self.files.insert(path.as_ref().to_path_buf(), content);
self
}
struct TestProject {
/// Temporary directory where the project is created
workspace_dir: PathBuf,
/// Original files when the project is created.
files: HashMap<PathBuf, String>,
/// File tree when the test is created.
file_tree: String,
}

fn create(mut self) -> Self {
let project_path = self.temp_dir.path();
let insta_path = workspace_path().join("insta");
impl TestProject {
fn new(files: HashMap<PathBuf, String>) -> TestProject {
let workspace_dir = TempDir::new().unwrap().into_path();

// Create files and replace $PROJECT_PATH in all files
for (path, content) in &self.files {
let full_path = project_path.join(path);
for (path, content) in &files {
let full_path = workspace_dir.join(path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).unwrap();
}
let replaced_content = content.replace("$PROJECT_PATH", insta_path.to_str().unwrap());
let replaced_content = content.replace("$PROJECT_PATH", insta_path().to_str().unwrap());
fs::write(full_path, replaced_content).unwrap();
}

self.project_path = Some(project_path.to_path_buf());
self
TestProject {
files,
file_tree: Self::current_file_tree(&workspace_dir),
workspace_dir,
}
}

fn cmd(&mut self) -> Command {
self.file_tree = Some(self.current_file_tree());
let project_path = self
.project_path
.as_ref()
.expect("Project has not been created yet. Call create() first.");
fn cmd(&self) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_cargo-insta"));
command.current_dir(project_path);
command.current_dir(self.workspace_dir.as_path());
// Use the same target directory as other tests, consistent across test
// run. This makes the compilation much faster (though do some tests
// tread on the toes of others? We could have a different cache for each
Expand All @@ -98,7 +124,7 @@ impl TestProject {

fn diff(&self, file_path: &str) -> String {
let original_content = self.files.get(Path::new(file_path)).unwrap();
let file_path_buf = self.project_path.as_ref().unwrap().join(file_path);
let file_path_buf = self.workspace_dir.join(file_path);
let updated_content = fs::read_to_string(&file_path_buf).unwrap();

unified_diff(
Expand All @@ -113,27 +139,30 @@ impl TestProject {
)
}

fn current_file_tree(&self) -> String {
WalkBuilder::new(&self.temp_dir)
fn current_file_tree(workspace_dir: &Path) -> String {
WalkBuilder::new(workspace_dir)
.filter_entry(|e| e.path().file_name() != Some(std::ffi::OsStr::new("target")))
.build()
.filter_map(|e| e.ok())
.sorted_by(|a, b| a.path().cmp(b.path()))
.map(|entry| {
let path = entry
.path()
.strip_prefix(&self.temp_dir)
.strip_prefix(workspace_dir)
.unwrap_or(entry.path());
format!("{}{}", " ".repeat(entry.depth()), path.display())
// Required for Windows compatibility
let path_str = path.to_str().map(|s| s.replace('\\', "/")).unwrap();
format!("{}{}", " ".repeat(entry.depth()), path_str)
})
.chain(std::iter::once(String::new()))
.collect::<Vec<_>>()
.join("\n")
}

fn file_tree_diff(&self) -> String {
unified_diff(
similar::Algorithm::Patience,
&self.file_tree.clone().unwrap(),
self.current_file_tree().as_ref(),
&self.file_tree.clone(),
Self::current_file_tree(&self.workspace_dir).as_ref(),
3,
Some(("Original file tree", "Updated file tree")),
)
Expand All @@ -142,7 +171,7 @@ impl TestProject {

#[test]
fn test_json_inline() {
let mut test_project = TestProject::new()
let test_project = TestFiles::new()
.add_file(
"Cargo.toml",
r#"
Expand Down Expand Up @@ -181,7 +210,7 @@ fn test_json_snapshot() {
"#
.to_string(),
)
.create();
.create_project();

let output = test_project
.cmd()
Expand Down Expand Up @@ -211,7 +240,7 @@ fn test_json_snapshot() {

#[test]
fn test_yaml_inline() {
let mut test_project = TestProject::new()
let test_project = TestFiles::new()
.add_file(
"Cargo.toml",
r#"
Expand Down Expand Up @@ -250,7 +279,7 @@ fn test_yaml_snapshot() {
"#
.to_string(),
)
.create();
.create_project();

let output = test_project
.cmd()
Expand Down Expand Up @@ -279,7 +308,7 @@ fn test_yaml_snapshot() {

#[test]
fn test_utf8_inline() {
let mut test_project = TestProject::new()
let test_project = TestFiles::new()
.add_file(
"Cargo.toml",
r#"
Expand Down Expand Up @@ -326,7 +355,7 @@ fn test_trailing_comma_in_inline_snapshot() {
"#
.to_string(),
)
.create();
.create_project();

let output = test_project
.cmd()
Expand Down Expand Up @@ -380,7 +409,7 @@ fn test_trailing_comma_in_inline_snapshot() {
#[ignore]
#[test]
fn test_nested_crate() {
let mut test_project = TestProject::new()
let test_project = TestFiles::new()
.add_file(
"Cargo.toml",
r#"
Expand Down Expand Up @@ -441,7 +470,7 @@ fn test_root() {
"#
.to_string(),
)
.create();
.create_project();

let output = test_project
.cmd()
Expand Down
Loading