Merge pull request #2349 from zed-industries/randomized-tests-operation-script

Restructure randomized integration test to allow editing and replaying a plan, fix discovered bugs
This commit is contained in:
Antonio Scandurra 2023-04-12 11:45:47 +02:00 committed by GitHub
commit 6d8635fa29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 2879 additions and 1863 deletions

View file

@ -4,11 +4,13 @@ use crate::{
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use client::proto::{self, PeerId};
use fs::LineEnding;
use gpui::{AppContext, AsyncAppContext, ModelHandle};
use language::{
point_from_lsp, point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Anchor, Bias, Buffer, CachedLspAdapter, PointUtf16, ToPointUtf16,
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
};
use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
@ -27,6 +29,8 @@ pub(crate) trait LspCommand: 'static + Sized {
fn to_lsp(
&self,
path: &Path,
buffer: &Buffer,
language_server: &Arc<LanguageServer>,
cx: &AppContext,
) -> <Self::LspRequest as lsp::request::Request>::Params;
async fn response_from_lsp(
@ -49,7 +53,7 @@ pub(crate) trait LspCommand: 'static + Sized {
project: &mut Project,
peer_id: PeerId,
buffer_version: &clock::Global,
cx: &AppContext,
cx: &mut AppContext,
) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
async fn response_from_proto(
self,
@ -91,6 +95,14 @@ pub(crate) struct GetHover {
pub position: PointUtf16,
}
pub(crate) struct GetCompletions {
pub position: PointUtf16,
}
pub(crate) struct GetCodeActions {
pub range: Range<Anchor>,
}
#[async_trait(?Send)]
impl LspCommand for PrepareRename {
type Response = Option<Range<Anchor>>;
@ -105,7 +117,13 @@ impl LspCommand for PrepareRename {
}
}
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::TextDocumentPositionParams {
lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
@ -161,9 +179,9 @@ impl LspCommand for PrepareRename {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
@ -175,7 +193,7 @@ impl LspCommand for PrepareRename {
_: &mut Project,
_: PeerId,
buffer_version: &clock::Global,
_: &AppContext,
_: &mut AppContext,
) -> proto::PrepareRenameResponse {
proto::PrepareRenameResponse {
can_rename: range.is_some(),
@ -199,9 +217,9 @@ impl LspCommand for PrepareRename {
if message.can_rename {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
let start = message.start.and_then(deserialize_anchor);
let end = message.end.and_then(deserialize_anchor);
Ok(start.zip(end).map(|(start, end)| start..end))
@ -221,7 +239,13 @@ impl LspCommand for PerformRename {
type LspRequest = lsp::request::Rename;
type ProtoRequest = proto::PerformRename;
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::RenameParams {
lsp::RenameParams {
text_document_position: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
@ -281,9 +305,9 @@ impl LspCommand for PerformRename {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
new_name: message.new_name,
@ -296,7 +320,7 @@ impl LspCommand for PerformRename {
project: &mut Project,
peer_id: PeerId,
_: &clock::Global,
cx: &AppContext,
cx: &mut AppContext,
) -> proto::PerformRenameResponse {
let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
proto::PerformRenameResponse {
@ -332,7 +356,13 @@ impl LspCommand for GetDefinition {
type LspRequest = lsp::request::GotoDefinition;
type ProtoRequest = proto::GetDefinition;
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoDefinitionParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::GotoDefinitionParams {
lsp::GotoDefinitionParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
@ -378,9 +408,9 @@ impl LspCommand for GetDefinition {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
})
@ -391,7 +421,7 @@ impl LspCommand for GetDefinition {
project: &mut Project,
peer_id: PeerId,
_: &clock::Global,
cx: &AppContext,
cx: &mut AppContext,
) -> proto::GetDefinitionResponse {
let links = location_links_to_proto(response, project, peer_id, cx);
proto::GetDefinitionResponse { links }
@ -418,7 +448,13 @@ impl LspCommand for GetTypeDefinition {
type LspRequest = lsp::request::GotoTypeDefinition;
type ProtoRequest = proto::GetTypeDefinition;
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoTypeDefinitionParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::GotoTypeDefinitionParams {
lsp::GotoTypeDefinitionParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
@ -464,9 +500,9 @@ impl LspCommand for GetTypeDefinition {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
})
@ -477,7 +513,7 @@ impl LspCommand for GetTypeDefinition {
project: &mut Project,
peer_id: PeerId,
_: &clock::Global,
cx: &AppContext,
cx: &mut AppContext,
) -> proto::GetTypeDefinitionResponse {
let links = location_links_to_proto(response, project, peer_id, cx);
proto::GetTypeDefinitionResponse { links }
@ -537,7 +573,7 @@ async fn location_links_from_proto(
.ok_or_else(|| anyhow!("missing origin end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
.await?;
Some(Location {
buffer,
range: start..end,
@ -562,7 +598,7 @@ async fn location_links_from_proto(
.ok_or_else(|| anyhow!("missing target end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
.await?;
let target = Location {
buffer,
range: start..end,
@ -658,7 +694,7 @@ fn location_links_to_proto(
links: Vec<LocationLink>,
project: &mut Project,
peer_id: PeerId,
cx: &AppContext,
cx: &mut AppContext,
) -> Vec<proto::LocationLink> {
links
.into_iter()
@ -693,7 +729,13 @@ impl LspCommand for GetReferences {
type LspRequest = lsp::request::References;
type ProtoRequest = proto::GetReferences;
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::ReferenceParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::ReferenceParams {
lsp::ReferenceParams {
text_document_position: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
@ -774,9 +816,9 @@ impl LspCommand for GetReferences {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
})
@ -787,7 +829,7 @@ impl LspCommand for GetReferences {
project: &mut Project,
peer_id: PeerId,
_: &clock::Global,
cx: &AppContext,
cx: &mut AppContext,
) -> proto::GetReferencesResponse {
let locations = response
.into_iter()
@ -827,7 +869,7 @@ impl LspCommand for GetReferences {
.ok_or_else(|| anyhow!("missing target end"))?;
target_buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
.await?;
locations.push(Location {
buffer: target_buffer,
range: start..end,
@ -851,7 +893,13 @@ impl LspCommand for GetDocumentHighlights {
capabilities.document_highlight_provider.is_some()
}
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::DocumentHighlightParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::DocumentHighlightParams {
lsp::DocumentHighlightParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
@ -915,9 +963,9 @@ impl LspCommand for GetDocumentHighlights {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
})
@ -928,7 +976,7 @@ impl LspCommand for GetDocumentHighlights {
_: &mut Project,
_: PeerId,
_: &clock::Global,
_: &AppContext,
_: &mut AppContext,
) -> proto::GetDocumentHighlightsResponse {
let highlights = response
.into_iter()
@ -965,7 +1013,7 @@ impl LspCommand for GetDocumentHighlights {
.ok_or_else(|| anyhow!("missing target end"))?;
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
.await;
.await?;
let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ,
@ -991,7 +1039,13 @@ impl LspCommand for GetHover {
type LspRequest = lsp::request::HoverRequest;
type ProtoRequest = proto::GetHover;
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::HoverParams {
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::HoverParams {
lsp::HoverParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
@ -1117,9 +1171,9 @@ impl LspCommand for GetHover {
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(message.version))
buffer.wait_for_version(deserialize_version(&message.version))
})
.await;
.await?;
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
})
@ -1130,7 +1184,7 @@ impl LspCommand for GetHover {
_: &mut Project,
_: PeerId,
_: &clock::Global,
_: &AppContext,
_: &mut AppContext,
) -> proto::GetHoverResponse {
if let Some(response) = response {
let (start, end) = if let Some(range) = response.range {
@ -1199,3 +1253,342 @@ impl LspCommand for GetHover {
message.buffer_id
}
}
#[async_trait(?Send)]
impl LspCommand for GetCompletions {
type Response = Vec<Completion>;
type LspRequest = lsp::request::Completion;
type ProtoRequest = proto::GetCompletions;
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::CompletionParams {
lsp::CompletionParams {
text_document_position: lsp::TextDocumentPositionParams::new(
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
point_to_lsp(self.position),
),
context: Default::default(),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
}
}
async fn response_from_lsp(
self,
completions: Option<lsp::CompletionResponse>,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
cx: AsyncAppContext,
) -> Result<Vec<Completion>> {
let completions = if let Some(completions) = completions {
match completions {
lsp::CompletionResponse::Array(completions) => completions,
lsp::CompletionResponse::List(list) => list.items,
}
} else {
Default::default()
};
let completions = buffer.read_with(&cx, |buffer, _| {
let language = buffer.language().cloned();
let snapshot = buffer.snapshot();
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
let mut range_for_token = None;
completions
.into_iter()
.filter_map(move |mut lsp_completion| {
// For now, we can only handle additional edits if they are returned
// when resolving the completion, not if they are present initially.
if lsp_completion
.additional_text_edits
.as_ref()
.map_or(false, |edits| !edits.is_empty())
{
return None;
}
let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
// If the language server provides a range to overwrite, then
// check that the range is valid.
Some(lsp::CompletionTextEdit::Edit(edit)) => {
let range = range_from_lsp(edit.range);
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
if start != range.start.0 || end != range.end.0 {
log::info!("completion out of expected range");
return None;
}
(
snapshot.anchor_before(start)..snapshot.anchor_after(end),
edit.new_text.clone(),
)
}
// If the language server does not provide a range, then infer
// the range based on the syntax tree.
None => {
if self.position != clipped_position {
log::info!("completion out of expected range");
return None;
}
let Range { start, end } = range_for_token
.get_or_insert_with(|| {
let offset = self.position.to_offset(&snapshot);
let (range, kind) = snapshot.surrounding_word(offset);
if kind == Some(CharKind::Word) {
range
} else {
offset..offset
}
})
.clone();
let text = lsp_completion
.insert_text
.as_ref()
.unwrap_or(&lsp_completion.label)
.clone();
(
snapshot.anchor_before(start)..snapshot.anchor_after(end),
text,
)
}
Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
log::info!("unsupported insert/replace completion");
return None;
}
};
let language = language.clone();
LineEnding::normalize(&mut new_text);
Some(async move {
let mut label = None;
if let Some(language) = language {
language.process_completion(&mut lsp_completion).await;
label = language.label_for_completion(&lsp_completion).await;
}
Completion {
old_range,
new_text,
label: label.unwrap_or_else(|| {
language::CodeLabel::plain(
lsp_completion.label.clone(),
lsp_completion.filter_text.as_deref(),
)
}),
lsp_completion,
}
})
})
});
Ok(futures::future::join_all(completions).await)
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCompletions {
let anchor = buffer.anchor_after(self.position);
proto::GetCompletions {
project_id,
buffer_id: buffer.remote_id(),
position: Some(language::proto::serialize_anchor(&anchor)),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
message: proto::GetCompletions,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
let version = deserialize_version(&message.version);
buffer
.update(&mut cx, |buffer, _| buffer.wait_for_version(version))
.await?;
let position = message
.position
.and_then(language::proto::deserialize_anchor)
.map(|p| {
buffer.read_with(&cx, |buffer, _| {
buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
})
})
.ok_or_else(|| anyhow!("invalid position"))?;
Ok(Self { position })
}
fn response_to_proto(
completions: Vec<Completion>,
_: &mut Project,
_: PeerId,
buffer_version: &clock::Global,
_: &mut AppContext,
) -> proto::GetCompletionsResponse {
proto::GetCompletionsResponse {
completions: completions
.iter()
.map(language::proto::serialize_completion)
.collect(),
version: serialize_version(&buffer_version),
}
}
async fn response_from_proto(
self,
message: proto::GetCompletionsResponse,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Vec<Completion>> {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})
.await?;
let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned());
let completions = message.completions.into_iter().map(|completion| {
language::proto::deserialize_completion(completion, language.clone())
});
futures::future::try_join_all(completions).await
}
fn buffer_id_from_proto(message: &proto::GetCompletions) -> u64 {
message.buffer_id
}
}
#[async_trait(?Send)]
impl LspCommand for GetCodeActions {
type Response = Vec<CodeAction>;
type LspRequest = lsp::request::CodeActionRequest;
type ProtoRequest = proto::GetCodeActions;
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
capabilities.code_action_provider.is_some()
}
fn to_lsp(
&self,
path: &Path,
buffer: &Buffer,
language_server: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::CodeActionParams {
let relevant_diagnostics = buffer
.snapshot()
.diagnostics_in_range::<_, usize>(self.range.clone(), false)
.map(|entry| entry.to_lsp_diagnostic_stub())
.collect();
lsp::CodeActionParams {
text_document: lsp::TextDocumentIdentifier::new(
lsp::Url::from_file_path(path).unwrap(),
),
range: range_to_lsp(self.range.to_point_utf16(buffer)),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
context: lsp::CodeActionContext {
diagnostics: relevant_diagnostics,
only: language_server.code_action_kinds(),
},
}
}
async fn response_from_lsp(
self,
actions: Option<lsp::CodeActionResponse>,
_: ModelHandle<Project>,
_: ModelHandle<Buffer>,
_: AsyncAppContext,
) -> Result<Vec<CodeAction>> {
Ok(actions
.unwrap_or_default()
.into_iter()
.filter_map(|entry| {
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
Some(CodeAction {
range: self.range.clone(),
lsp_action,
})
} else {
None
}
})
.collect())
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeActions {
proto::GetCodeActions {
project_id,
buffer_id: buffer.remote_id(),
start: Some(language::proto::serialize_anchor(&self.range.start)),
end: Some(language::proto::serialize_anchor(&self.range.end)),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
message: proto::GetCodeActions,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
let start = message
.start
.and_then(language::proto::deserialize_anchor)
.ok_or_else(|| anyhow!("invalid start"))?;
let end = message
.end
.and_then(language::proto::deserialize_anchor)
.ok_or_else(|| anyhow!("invalid end"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})
.await?;
Ok(Self { range: start..end })
}
fn response_to_proto(
code_actions: Vec<CodeAction>,
_: &mut Project,
_: PeerId,
buffer_version: &clock::Global,
_: &mut AppContext,
) -> proto::GetCodeActionsResponse {
proto::GetCodeActionsResponse {
actions: code_actions
.iter()
.map(language::proto::serialize_code_action)
.collect(),
version: serialize_version(&buffer_version),
}
}
async fn response_from_proto(
self,
message: proto::GetCodeActionsResponse,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Vec<CodeAction>> {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})
.await?;
message
.actions
.into_iter()
.map(language::proto::deserialize_code_action)
.collect()
}
fn buffer_id_from_proto(message: &proto::GetCodeActions) -> u64 {
message.buffer_id
}
}

File diff suppressed because it is too large Load diff

View file

@ -221,7 +221,7 @@ impl Worktree {
root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
entries_by_path: Default::default(),
entries_by_id: Default::default(),
scan_id: 0,
scan_id: 1,
completed_scan_id: 0,
},
};
@ -298,7 +298,7 @@ impl Worktree {
.collect(),
entries_by_path: Default::default(),
entries_by_id: Default::default(),
scan_id: 0,
scan_id: 1,
completed_scan_id: 0,
};
@ -1063,7 +1063,7 @@ impl RemoteWorktree {
version: serialize_version(&version),
})
.await?;
let version = deserialize_version(response.version);
let version = deserialize_version(&response.version);
let fingerprint = deserialize_fingerprint(&response.fingerprint)?;
let mtime = response
.mtime
@ -1223,11 +1223,10 @@ impl Snapshot {
let mut entries_by_path_edits = Vec::new();
let mut entries_by_id_edits = Vec::new();
for entry_id in update.removed_entries {
let entry = self
.entry_for_id(ProjectEntryId::from_proto(entry_id))
.ok_or_else(|| anyhow!("unknown entry {}", entry_id))?;
entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
entries_by_id_edits.push(Edit::Remove(entry.id));
if let Some(entry) = self.entry_for_id(ProjectEntryId::from_proto(entry_id)) {
entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
entries_by_id_edits.push(Edit::Remove(entry.id));
}
}
for entry in update.updated_entries {
@ -3725,7 +3724,7 @@ mod tests {
) {
let mut files = Vec::new();
let mut dirs = Vec::new();
for path in fs.as_fake().paths().await {
for path in fs.as_fake().paths() {
if path.starts_with(root_path) {
if fs.is_file(&path).await {
files.push(path);