Skip to content

Commit

Permalink
use temporary files for writes
Browse files Browse the repository at this point in the history
  • Loading branch information
kirawi committed Jan 5, 2024
1 parent da4afaf commit 86eb32a
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 50 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.

90 changes: 43 additions & 47 deletions helix-term/tests/test/commands/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
.await?;

// verify if writes are queued up, it finishes them before closing the buffer
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut command = String::new();
const RANGE: RangeInclusive<i32> = 1..=1000;

Expand All @@ -92,15 +92,16 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
false,
)
.await?;
let (mut file, _) = reload_file(file);

helpers::assert_file_has_content(file.as_file_mut(), &platform_line(&RANGE.end().to_string()))?;
helpers::assert_file_has_content(&mut file, &platform_line(&RANGE.end().to_string()))?;

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_write() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.build()?;
Expand All @@ -113,11 +114,9 @@ async fn test_write() -> anyhow::Result<()> {
)
.await?;

file.as_file_mut().flush()?;
file.as_file_mut().sync_all()?;

let (mut file, _) = reload_file(file);
let mut file_content = String::new();
file.as_file_mut().read_to_string(&mut file_content)?;
file.read_to_string(&mut file_content)?;

assert_eq!(
helpers::platform_line("the gostak distims the doshes"),
Expand All @@ -144,12 +143,9 @@ async fn test_overwrite_protection() -> anyhow::Result<()> {

test_key_sequence(&mut app, Some(":x<ret>"), None, false).await?;

file.as_file_mut().flush()?;
file.as_file_mut().sync_all()?;

file.rewind()?;
let (mut file, _) = reload_file(file);
let mut file_content = String::new();
file.as_file_mut().read_to_string(&mut file_content)?;
file.read_to_string(&mut file_content)?;

assert_eq!(
helpers::platform_line("extremely important content"),
Expand All @@ -161,7 +157,7 @@ async fn test_overwrite_protection() -> anyhow::Result<()> {

#[tokio::test(flavor = "multi_thread")]
async fn test_write_quit() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.build()?;
Expand All @@ -174,23 +170,22 @@ async fn test_write_quit() -> anyhow::Result<()> {
)
.await?;

file.as_file_mut().flush()?;
file.as_file_mut().sync_all()?;
let (mut file, _) = reload_file(file);

let mut file_content = String::new();
file.as_file_mut().read_to_string(&mut file_content)?;
file.read_to_string(&mut file_content)?;

assert_eq!(
helpers::platform_line("the gostak distims the doshes"),
file_content
file_content,
);

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_write_concurrent() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut command = String::new();
const RANGE: RangeInclusive<i32> = 1..=1000;
let mut app = helpers::AppBuilder::new()
Expand All @@ -204,11 +199,9 @@ async fn test_write_concurrent() -> anyhow::Result<()> {

test_key_sequence(&mut app, Some(&command), None, false).await?;

file.as_file_mut().flush()?;
file.as_file_mut().sync_all()?;

let (mut file, _) = reload_file(file);
let mut file_content = String::new();
file.as_file_mut().read_to_string(&mut file_content)?;
file.read_to_string(&mut file_content)?;
assert_eq!(platform_line(&RANGE.end().to_string()), file_content);

Ok(())
Expand Down Expand Up @@ -257,7 +250,7 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> {

#[tokio::test(flavor = "multi_thread")]
async fn test_write_scratch_to_new_path() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;

test_key_sequence(
&mut AppBuilder::new().build()?,
Expand All @@ -275,7 +268,8 @@ async fn test_write_scratch_to_new_path() -> anyhow::Result<()> {
)
.await?;

helpers::assert_file_has_content(file.as_file_mut(), &helpers::platform_line("hello"))?;
let (mut file, _) = reload_file(file);
helpers::assert_file_has_content(&mut file, &helpers::platform_line("hello"))?;

Ok(())
}
Expand Down Expand Up @@ -303,7 +297,7 @@ async fn test_write_scratch_no_path_fails() -> anyhow::Result<()> {

#[tokio::test(flavor = "multi_thread")]
async fn test_write_auto_format_fails_still_writes() -> anyhow::Result<()> {
let mut file = tempfile::Builder::new().suffix(".rs").tempfile()?;
let file = tempfile::Builder::new().suffix(".rs").tempfile()?;

let lang_conf = indoc! {r#"
[[language]]
Expand All @@ -319,16 +313,17 @@ async fn test_write_auto_format_fails_still_writes() -> anyhow::Result<()> {

test_key_sequences(&mut app, vec![(Some(":w<ret>"), None)], false).await?;

let (mut file, _) = reload_file(file);
// file still saves
helpers::assert_file_has_content(file.as_file_mut(), "let foo = 0;\n")?;
helpers::assert_file_has_content(&mut file, "let foo = 0;\n")?;

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_write_new_path() -> anyhow::Result<()> {
let mut file1 = tempfile::NamedTempFile::new().unwrap();
let mut file2 = tempfile::NamedTempFile::new().unwrap();
let file1 = tempfile::NamedTempFile::new().unwrap();
let file2 = tempfile::NamedTempFile::new().unwrap();
let mut app = helpers::AppBuilder::new()
.with_file(file1.path(), None)
.build()?;
Expand Down Expand Up @@ -358,13 +353,15 @@ async fn test_write_new_path() -> anyhow::Result<()> {
)
.await?;

let (mut file1, _) = reload_file(file1);
let (mut file2, _) = reload_file(file2);
helpers::assert_file_has_content(
file1.as_file_mut(),
&mut file1,
&helpers::platform_line("i can eat glass, it will not hurt me\n"),
)?;

helpers::assert_file_has_content(
file2.as_file_mut(),
&mut file2,
&helpers::platform_line("i can eat glass, it will not hurt me\n"),
)?;

Expand Down Expand Up @@ -426,16 +423,17 @@ async fn test_write_utf_bom_file() -> anyhow::Result<()> {

#[tokio::test(flavor = "multi_thread")]
async fn test_write_insert_final_newline_added_if_missing() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.with_input_text("#[h|]#ave you tried chamomile tea?")
.build()?;

test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;

let (mut file, _) = reload_file(file);
helpers::assert_file_has_content(
file.as_file_mut(),
&mut file,
&helpers::platform_line("have you tried chamomile tea?\n"),
)?;

Expand All @@ -444,25 +442,23 @@ async fn test_write_insert_final_newline_added_if_missing() -> anyhow::Result<()

#[tokio::test(flavor = "multi_thread")]
async fn test_write_insert_final_newline_unchanged_if_not_missing() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.with_input_text(&helpers::platform_line("#[t|]#en minutes, please\n"))
.build()?;

test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;

helpers::assert_file_has_content(
file.as_file_mut(),
&helpers::platform_line("ten minutes, please\n"),
)?;
let (mut file, _) = reload_file(file);
helpers::assert_file_has_content(&mut file, &helpers::platform_line("ten minutes, please\n"))?;

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_write_insert_final_newline_unchanged_if_missing_and_false() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_config(Config {
editor: helix_view::editor::Config {
Expand All @@ -477,18 +473,16 @@ async fn test_write_insert_final_newline_unchanged_if_missing_and_false() -> any

test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;

helpers::assert_file_has_content(
file.as_file_mut(),
"the quiet rain continued through the night",
)?;
let (mut file, _) = reload_file(file);
helpers::assert_file_has_content(&mut file, "the quiet rain continued through the night")?;

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_write_all_insert_final_newline_add_if_missing_and_modified() -> anyhow::Result<()> {
let mut file1 = tempfile::NamedTempFile::new()?;
let mut file2 = tempfile::NamedTempFile::new()?;
let file1 = tempfile::NamedTempFile::new()?;
let file2 = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file1.path(), None)
.with_input_text("#[w|]#e don't serve time travelers here")
Expand All @@ -505,13 +499,15 @@ async fn test_write_all_insert_final_newline_add_if_missing_and_modified() -> an
)
.await?;

let (mut file1, _) = reload_file(file1);
let (mut file2, _) = reload_file(file2);
helpers::assert_file_has_content(
file1.as_file_mut(),
&mut file1,
&helpers::platform_line("we don't serve time travelers here\n"),
)?;

helpers::assert_file_has_content(
file2.as_file_mut(),
&mut file2,
&helpers::platform_line("a time traveler walks into a bar\n"),
)?;

Expand Down Expand Up @@ -556,7 +552,7 @@ async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> {
)
.await?;

file.rewind()?;
let (mut file, _) = reload_file(file);
let mut new_file_content: Vec<u8> = Vec::new();
file.read_to_end(&mut new_file_content)?;

Expand Down
8 changes: 7 additions & 1 deletion helix-term/tests/test/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crossterm::event::{Event, KeyEvent};
use helix_core::{diagnostic::Severity, test, Selection, Transaction};
use helix_term::{application::Application, args::Args, config::Config, keymap::merge_keys};
use helix_view::{current_ref, doc, editor::LspConfig, input::parse_macro, Editor};
use tempfile::NamedTempFile;
use tempfile::{NamedTempFile, TempPath};
use tokio_stream::wrappers::UnboundedReceiverStream;

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -368,3 +368,9 @@ pub fn assert_status_not_error(editor: &Editor) {
assert_ne!(&Severity::Error, sev);
}
}

pub fn reload_file(t: NamedTempFile) -> (File, TempPath) {
let p = t.into_temp_path();
let f = std::fs::File::open(&p).unwrap();
(f, p)
}
2 changes: 2 additions & 0 deletions helix-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ bitflags = "2.4"
anyhow = "1"
crossterm = { version = "0.27", optional = true }

tempfile = "3.9"

# Conversion traits
once_cell = "1.19"
url = "2.5.0"
Expand Down
13 changes: 11 additions & 2 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,8 +897,17 @@ impl Document {
}
}

let mut file = File::create(&path).await?;
to_writer(&mut file, encoding_with_bom_info, &text).await?;
// TODO: Fail if path is read-only.
let (mut tmp_file, tmp_path) = tokio::task::spawn_blocking(
move || -> anyhow::Result<(File, tempfile::TempPath)> {
let (f, p) = tempfile::NamedTempFile::new()?.into_parts();
Ok((tokio::fs::File::from_std(f), p))
},
)
.await??;

to_writer(&mut tmp_file, encoding_with_bom_info, &text).await?;
tokio::fs::rename(tmp_path, &path).await?;

let event = DocumentSavedEvent {
revision: current_rev,
Expand Down

0 comments on commit 86eb32a

Please sign in to comment.