fix deltas calcuation & add more tests

This commit is contained in:
Nikita Galaiko 2023-04-19 19:53:57 +02:00
parent f866538b4f
commit 92b1c00e58
2 changed files with 179 additions and 34 deletions

View File

@ -88,27 +88,32 @@ impl<'listener> Handler<'listener> {
}
}
fn get_latest_file_from_previous_session(&self, path: &str) -> Result<Option<String>> {
match self
.gb_repository
.get_sessions_iterator()
.context("failed to get sessions iterator")?
.next()
{
Some(Ok(head_session)) => {
let session_path = std::path::Path::new("wd").join(path);
let head_session_reader = self.gb_repository.get_session_reader(&head_session)?;
fn get_latest_file_contents(
&self,
project_repository: &project_repository::Repository,
path: &std::path::Path,
) -> Result<Option<String>> {
let path = path.to_str().unwrap();
match self.gb_repository.git_repository.head() {
Ok(head) => {
let head_commit = head.peel_to_commit()?;
let commit_reader = reader::CommitReader::from_commit(
&self.gb_repository.git_repository,
head_commit,
)
.context("failed to get commit reader")?;
if !head_session_reader.exists(session_path.to_str().unwrap()) {
let session_path = std::path::Path::new("wd").join(path);
if !commit_reader.exists(session_path.to_str().unwrap()) {
return Ok(None);
}
if head_session_reader.size(session_path.to_str().unwrap())? > 100_000 {
if commit_reader.size(session_path.to_str().unwrap())? > 100_000 {
log::warn!("{}: ignoring large file: {}", self.project_id, path);
return Ok(None);
}
match head_session_reader.read(session_path.to_str().unwrap())? {
match commit_reader.read(session_path.to_str().unwrap())? {
reader::Content::UTF8(content) => Ok(Some(content)),
reader::Content::Binary(_) => {
log::warn!("{}: ignoring non-utf8 file: {}", self.project_id, path);
@ -116,24 +121,12 @@ impl<'listener> Handler<'listener> {
}
}
}
Some(Err(err)) => Err(err).context("failed to get head session"),
None => Ok(None),
}
}
fn get_latest_file_contents(
&self,
project_repository: &project_repository::Repository,
path: &std::path::Path,
) -> Result<Option<String>> {
let path = path.to_str().unwrap();
match self
.get_latest_file_from_previous_session(path)
.context("failed to get latest file from previous session")?
{
Some(content) => Ok(Some(content)),
None => self.get_latest_file_from_repository_head(project_repository, path),
Err(err) => {
if err.code() == git2::ErrorCode::UnbornBranch {
return self.get_latest_file_from_repository_head(project_repository, path);
}
Err(err).context("failed to get head")?
}
}
}

View File

@ -311,7 +311,7 @@ fn test_register_file_delted() -> Result<()> {
}
#[test]
fn test_flow() -> Result<()> {
fn test_flow_with_commits() -> Result<()> {
let repository = test_repository()?;
let project = test_project(&repository)?;
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
@ -337,8 +337,9 @@ fn test_flow() -> Result<()> {
i.to_string(),
)?;
commit_all(&repository)?;
listener.handle(&relative_file_path)?;
gb_repo.flush()?;
assert!(gb_repo.flush()?.is_some());
}
// get all the created sessions
@ -399,6 +400,157 @@ fn test_flow() -> Result<()> {
assert_eq!(text.iter().collect::<String>(), size.to_string());
}
Ok(())
}
#[test]
fn test_flow_no_commits() -> Result<()> {
let repository = test_repository()?;
let project = test_project(&repository)?;
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
let storage = storage::Storage::from_path(tempdir()?.path().to_path_buf());
let user_store = users::Storage::new(storage.clone());
let project_store = projects::Storage::new(storage);
project_store.add_project(&project)?;
let gb_repo = gb_repository::Repository::open(
gb_repo_path,
project.id.clone(),
project_store.clone(),
user_store,
)?;
let listener = Handler::new(project.id.clone(), project_store, &gb_repo);
let size = 10;
let relative_file_path = std::path::Path::new("one/two/test.txt");
for i in 1..=size {
std::fs::create_dir_all(std::path::Path::new(&project.path).join("one/two"))?;
// create a session with a single file change and flush it
std::fs::write(
std::path::Path::new(&project.path).join(relative_file_path),
i.to_string(),
)?;
listener.handle(&relative_file_path)?;
assert!(gb_repo.flush()?.is_some());
}
// get all the created sessions
let mut sessions: Vec<sessions::Session> = gb_repo
.get_sessions_iterator()?
.map(|s| s.unwrap())
.collect();
assert_eq!(sessions.len(), size);
// verify sessions order is correct
let mut last_start = sessions[0].meta.start_timestamp_ms;
let mut last_end = sessions[0].meta.start_timestamp_ms;
sessions[1..].iter().for_each(|session| {
assert!(session.meta.start_timestamp_ms < last_start);
assert!(session.meta.last_timestamp_ms < last_end);
last_start = session.meta.start_timestamp_ms;
last_end = session.meta.last_timestamp_ms;
});
sessions.reverse();
// try to reconstruct file state from operations for every session slice
for i in 0..=sessions.len() - 1 {
let sessions_slice = &mut sessions[i..];
// collect all operations from sessions in the reverse order
let mut operations: Vec<deltas::Operation> = vec![];
sessions_slice.iter().for_each(|session| {
let reader = gb_repo.get_session_reader(&session).unwrap();
let deltas_by_filepath = reader.deltas(None).unwrap();
for deltas in deltas_by_filepath.values() {
deltas.iter().for_each(|delta| {
delta.operations.iter().for_each(|operation| {
operations.push(operation.clone());
});
});
}
});
let reader = gb_repo
.get_session_reader(&sessions_slice.first().unwrap())
.unwrap();
let files = reader.files(None).unwrap();
if i == 0 {
assert_eq!(files.len(), 0);
} else {
assert_eq!(files.len(), 1);
}
let base_file = files.get(&relative_file_path.to_str().unwrap().to_string());
let mut text: Vec<char> = match base_file {
Some(file) => file.chars().collect(),
None => vec![],
};
for operation in operations {
operation.apply(&mut text).unwrap();
}
assert_eq!(text.iter().collect::<String>(), size.to_string());
}
Ok(())
}
#[test]
fn test_flow_signle_session() -> Result<()> {
let repository = test_repository()?;
let project = test_project(&repository)?;
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
let storage = storage::Storage::from_path(tempdir()?.path().to_path_buf());
let user_store = users::Storage::new(storage.clone());
let project_store = projects::Storage::new(storage);
project_store.add_project(&project)?;
let gb_repo = gb_repository::Repository::open(
gb_repo_path,
project.id.clone(),
project_store.clone(),
user_store,
)?;
let listener = Handler::new(project.id.clone(), project_store, &gb_repo);
let size = 10;
let relative_file_path = std::path::Path::new("one/two/test.txt");
for i in 1..=size {
std::fs::create_dir_all(std::path::Path::new(&project.path).join("one/two"))?;
// create a session with a single file change and flush it
std::fs::write(
std::path::Path::new(&project.path).join(relative_file_path),
i.to_string(),
)?;
listener.handle(&relative_file_path)?;
}
// collect all operations from sessions in the reverse order
let mut operations: Vec<deltas::Operation> = vec![];
let session = gb_repo.get_current_session()?.unwrap();
let reader = gb_repo.get_session_reader(&session).unwrap();
let deltas_by_filepath = reader.deltas(None).unwrap();
for deltas in deltas_by_filepath.values() {
deltas.iter().for_each(|delta| {
delta.operations.iter().for_each(|operation| {
operations.push(operation.clone());
});
});
}
let reader = gb_repo.get_session_reader(&session).unwrap();
let files = reader.files(None).unwrap();
let base_file = files.get(&relative_file_path.to_str().unwrap().to_string());
let mut text: Vec<char> = match base_file {
Some(file) => file.chars().collect(),
None => vec![],
};
for operation in operations {
operation.apply(&mut text).unwrap();
}
assert_eq!(text.iter().collect::<String>(), size.to_string());
Ok(())
}