1use std::path::{Path, PathBuf};
2use std::time::SystemTime;
3use std::{env, io};
4
5use async_trait::async_trait;
6use distant_core::protocol::semver;
7use distant_core::protocol::{
8 ChangeKind, ChangeKindSet, DirEntry, Environment, FileType, Metadata, Permissions, ProcessId,
9 PtySize, SearchId, SearchQuery, SetPermissionsOptions, SystemInfo, Version, PROTOCOL_VERSION,
10};
11use distant_core::{DistantApi, DistantCtx};
12use ignore::{DirEntry as WalkDirEntry, WalkBuilder};
13use log::*;
14use tokio::io::AsyncWriteExt;
15use walkdir::WalkDir;
16
17use crate::config::Config;
18
19mod process;
20mod state;
21use state::*;
22
23pub struct Api {
28 state: GlobalState,
29}
30
31impl Api {
32 pub fn initialize(config: Config) -> io::Result<Self> {
34 Ok(Self {
35 state: GlobalState::initialize(config)?,
36 })
37 }
38}
39
40#[async_trait]
41impl DistantApi for Api {
42 async fn read_file(&self, ctx: DistantCtx, path: PathBuf) -> io::Result<Vec<u8>> {
43 debug!(
44 "[Conn {}] Reading bytes from file {:?}",
45 ctx.connection_id, path
46 );
47
48 tokio::fs::read(path).await
49 }
50
51 async fn read_file_text(&self, ctx: DistantCtx, path: PathBuf) -> io::Result<String> {
52 debug!(
53 "[Conn {}] Reading text from file {:?}",
54 ctx.connection_id, path
55 );
56
57 tokio::fs::read_to_string(path).await
58 }
59
60 async fn write_file(&self, ctx: DistantCtx, path: PathBuf, data: Vec<u8>) -> io::Result<()> {
61 debug!(
62 "[Conn {}] Writing bytes to file {:?}",
63 ctx.connection_id, path
64 );
65
66 tokio::fs::write(path, data).await
67 }
68
69 async fn write_file_text(
70 &self,
71 ctx: DistantCtx,
72 path: PathBuf,
73 data: String,
74 ) -> io::Result<()> {
75 debug!(
76 "[Conn {}] Writing text to file {:?}",
77 ctx.connection_id, path
78 );
79
80 tokio::fs::write(path, data).await
81 }
82
83 async fn append_file(&self, ctx: DistantCtx, path: PathBuf, data: Vec<u8>) -> io::Result<()> {
84 debug!(
85 "[Conn {}] Appending bytes to file {:?}",
86 ctx.connection_id, path
87 );
88
89 let mut file = tokio::fs::OpenOptions::new()
90 .create(true)
91 .append(true)
92 .open(path)
93 .await?;
94 file.write_all(data.as_ref()).await
95 }
96
97 async fn append_file_text(
98 &self,
99 ctx: DistantCtx,
100 path: PathBuf,
101 data: String,
102 ) -> io::Result<()> {
103 debug!(
104 "[Conn {}] Appending text to file {:?}",
105 ctx.connection_id, path
106 );
107
108 let mut file = tokio::fs::OpenOptions::new()
109 .create(true)
110 .append(true)
111 .open(path)
112 .await?;
113 file.write_all(data.as_ref()).await
114 }
115
116 async fn read_dir(
117 &self,
118 ctx: DistantCtx,
119 path: PathBuf,
120 depth: usize,
121 absolute: bool,
122 canonicalize: bool,
123 include_root: bool,
124 ) -> io::Result<(Vec<DirEntry>, Vec<io::Error>)> {
125 debug!(
126 "[Conn {}] Reading directory {:?} {{depth: {}, absolute: {}, canonicalize: {}, include_root: {}}}",
127 ctx.connection_id, path, depth, absolute, canonicalize, include_root
128 );
129
130 let root_path = tokio::fs::canonicalize(path).await?;
132
133 let dir = WalkDir::new(root_path.as_path())
136 .min_depth(usize::from(!include_root))
137 .sort_by_file_name();
138
139 let dir = if depth > 0 { dir.max_depth(depth) } else { dir };
142
143 let mut entries = Vec::new();
145 let mut errors = Vec::new();
146
147 #[inline]
148 fn map_file_type(ft: std::fs::FileType) -> FileType {
149 if ft.is_dir() {
150 FileType::Dir
151 } else if ft.is_file() {
152 FileType::File
153 } else {
154 FileType::Symlink
155 }
156 }
157
158 for entry in dir {
159 match entry.map_err(io::Error::from) {
160 Ok(e) if e.depth() > 0 => {
162 let mut path = if canonicalize {
165 match tokio::fs::canonicalize(e.path()).await {
166 Ok(path) => path,
167 Err(x) => {
168 errors.push(x);
169 continue;
170 }
171 }
172 } else {
173 e.path().to_path_buf()
174 };
175
176 if !absolute {
178 path = path
183 .strip_prefix(root_path.as_path())
184 .map(Path::to_path_buf)
185 .unwrap_or(path);
186 };
187
188 entries.push(DirEntry {
189 path,
190 file_type: map_file_type(e.file_type()),
191 depth: e.depth(),
192 });
193 }
194
195 Ok(e) => {
197 entries.push(DirEntry {
198 path: e.path().to_path_buf(),
199 file_type: map_file_type(e.file_type()),
200 depth: e.depth(),
201 });
202 }
203
204 Err(x) => errors.push(x),
205 }
206 }
207
208 Ok((entries, errors))
209 }
210
211 async fn create_dir(&self, ctx: DistantCtx, path: PathBuf, all: bool) -> io::Result<()> {
212 debug!(
213 "[Conn {}] Creating directory {:?} {{all: {}}}",
214 ctx.connection_id, path, all
215 );
216 if all {
217 tokio::fs::create_dir_all(path).await
218 } else {
219 tokio::fs::create_dir(path).await
220 }
221 }
222
223 async fn remove(&self, ctx: DistantCtx, path: PathBuf, force: bool) -> io::Result<()> {
224 debug!(
225 "[Conn {}] Removing {:?} {{force: {}}}",
226 ctx.connection_id, path, force
227 );
228 let path_metadata = tokio::fs::metadata(path.as_path()).await?;
229 if path_metadata.is_dir() {
230 if force {
231 tokio::fs::remove_dir_all(path).await
232 } else {
233 tokio::fs::remove_dir(path).await
234 }
235 } else {
236 tokio::fs::remove_file(path).await
237 }
238 }
239
240 async fn copy(&self, ctx: DistantCtx, src: PathBuf, dst: PathBuf) -> io::Result<()> {
241 debug!(
242 "[Conn {}] Copying {:?} to {:?}",
243 ctx.connection_id, src, dst
244 );
245 let src_metadata = tokio::fs::metadata(src.as_path()).await?;
246 if src_metadata.is_dir() {
247 tokio::fs::create_dir_all(dst.as_path()).await?;
250
251 for entry in WalkDir::new(src.as_path())
252 .min_depth(1)
253 .follow_links(false)
254 .into_iter()
255 .filter_entry(|e| {
256 e.file_type().is_file() || e.file_type().is_dir() || e.path_is_symlink()
257 })
258 {
259 let entry = entry?;
260
261 let local_src = entry.path().strip_prefix(src.as_path()).unwrap();
265
266 let local_src_file_name = local_src.file_name().unwrap();
268
269 let local_src_dir = local_src.parent().unwrap();
272
273 let dst_parent_dir = dst.join(local_src_dir);
275
276 tokio::fs::create_dir_all(dst_parent_dir.as_path()).await?;
278
279 let dst_path = dst_parent_dir.join(local_src_file_name);
280
281 if !entry.file_type().is_dir() {
283 tokio::fs::copy(entry.path(), dst_path).await?;
284
285 } else {
287 tokio::fs::create_dir(dst_path).await?;
288 }
289 }
290 } else {
291 tokio::fs::copy(src, dst).await?;
292 }
293
294 Ok(())
295 }
296
297 async fn rename(&self, ctx: DistantCtx, src: PathBuf, dst: PathBuf) -> io::Result<()> {
298 debug!(
299 "[Conn {}] Renaming {:?} to {:?}",
300 ctx.connection_id, src, dst
301 );
302 tokio::fs::rename(src, dst).await
303 }
304
305 async fn watch(
306 &self,
307 ctx: DistantCtx,
308 path: PathBuf,
309 recursive: bool,
310 only: Vec<ChangeKind>,
311 except: Vec<ChangeKind>,
312 ) -> io::Result<()> {
313 let only = only.into_iter().collect::<ChangeKindSet>();
314 let except = except.into_iter().collect::<ChangeKindSet>();
315 debug!(
316 "[Conn {}] Watching {:?} {{recursive: {}, only: {}, except: {}}}",
317 ctx.connection_id, path, recursive, only, except
318 );
319
320 let path = RegisteredPath::register(
321 ctx.connection_id,
322 path.as_path(),
323 recursive,
324 only,
325 except,
326 ctx.reply,
327 )
328 .await?;
329
330 self.state.watcher.watch(path).await?;
331
332 Ok(())
333 }
334
335 async fn unwatch(&self, ctx: DistantCtx, path: PathBuf) -> io::Result<()> {
336 debug!("[Conn {}] Unwatching {:?}", ctx.connection_id, path);
337
338 self.state
339 .watcher
340 .unwatch(ctx.connection_id, path.as_path())
341 .await?;
342 Ok(())
343 }
344
345 async fn exists(&self, ctx: DistantCtx, path: PathBuf) -> io::Result<bool> {
346 debug!("[Conn {}] Checking if {:?} exists", ctx.connection_id, path);
347
348 match tokio::fs::metadata(path.as_path()).await {
351 Ok(_) => Ok(true),
352 Err(x) if x.kind() == io::ErrorKind::NotFound => Ok(false),
353 Err(x) => return Err(x),
354 }
355 }
356
357 async fn metadata(
358 &self,
359 ctx: DistantCtx,
360 path: PathBuf,
361 canonicalize: bool,
362 resolve_file_type: bool,
363 ) -> io::Result<Metadata> {
364 debug!(
365 "[Conn {}] Reading metadata for {:?} {{canonicalize: {}, resolve_file_type: {}}}",
366 ctx.connection_id, path, canonicalize, resolve_file_type
367 );
368 let metadata = tokio::fs::symlink_metadata(path.as_path()).await?;
369 let canonicalized_path = if canonicalize {
370 Some(tokio::fs::canonicalize(path.as_path()).await?)
371 } else {
372 None
373 };
374
375 let file_type = if resolve_file_type && metadata.file_type().is_symlink() {
378 tokio::fs::metadata(path).await?.file_type()
379 } else {
380 metadata.file_type()
381 };
382
383 Ok(Metadata {
384 canonicalized_path,
385 accessed: metadata
386 .accessed()
387 .ok()
388 .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
389 .map(|d| d.as_secs()),
390 created: metadata
391 .created()
392 .ok()
393 .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
394 .map(|d| d.as_secs()),
395 modified: metadata
396 .modified()
397 .ok()
398 .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
399 .map(|d| d.as_secs()),
400 len: metadata.len(),
401 readonly: metadata.permissions().readonly(),
402 file_type: if file_type.is_dir() {
403 FileType::Dir
404 } else if file_type.is_file() {
405 FileType::File
406 } else {
407 FileType::Symlink
408 },
409
410 #[cfg(unix)]
411 unix: Some({
412 use std::os::unix::prelude::*;
413 let mode = metadata.mode();
414 distant_core::protocol::UnixMetadata::from(mode)
415 }),
416 #[cfg(not(unix))]
417 unix: None,
418
419 #[cfg(windows)]
420 windows: Some({
421 use std::os::windows::prelude::*;
422 let attributes = metadata.file_attributes();
423 distant_core::protocol::WindowsMetadata::from(attributes)
424 }),
425 #[cfg(not(windows))]
426 windows: None,
427 })
428 }
429
430 async fn set_permissions(
431 &self,
432 _ctx: DistantCtx,
433 path: PathBuf,
434 permissions: Permissions,
435 options: SetPermissionsOptions,
436 ) -> io::Result<()> {
437 fn build_permissions(
439 entry: &WalkDirEntry,
440 permissions: &Permissions,
441 ) -> io::Result<std::fs::Permissions> {
442 let mut std_permissions = entry
444 .metadata()
445 .map_err(|x| match x.io_error() {
446 Some(x) => io::Error::new(x.kind(), format!("(Read permissions failed) {x}")),
447 None => io::Error::new(
448 io::ErrorKind::Other,
449 format!("(Read permissions failed) {x}"),
450 ),
451 })?
452 .permissions();
453
454 if let Some(readonly) = permissions.is_readonly() {
456 std_permissions.set_readonly(readonly);
457 }
458
459 #[cfg(unix)]
461 {
462 use std::os::unix::prelude::*;
463 let mut current = Permissions::from(std_permissions.clone());
464 current.apply_from(permissions);
465 std_permissions.set_mode(current.to_unix_mode());
466 }
467
468 Ok(std_permissions)
469 }
470
471 async fn set_permissions_impl(
472 entry: &WalkDirEntry,
473 permissions: &Permissions,
474 ) -> io::Result<()> {
475 let permissions = match permissions.is_complete() {
476 #[cfg(unix)]
480 true => std::fs::Permissions::from(*permissions),
481
482 _ => build_permissions(entry, permissions)?,
485 };
486
487 if log_enabled!(Level::Trace) {
488 let mut output = String::new();
489 output.push_str("readonly = ");
490 output.push_str(if permissions.readonly() {
491 "true"
492 } else {
493 "false"
494 });
495
496 #[cfg(unix)]
497 {
498 use std::os::unix::prelude::*;
499 output.push_str(&format!(", mode = {:#o}", permissions.mode()));
500 }
501
502 trace!("Setting {:?} permissions to ({})", entry.path(), output);
503 }
504
505 tokio::fs::set_permissions(entry.path(), permissions)
506 .await
507 .map_err(|x| io::Error::new(x.kind(), format!("(Set permissions failed) {x}")))
508 }
509
510 let path = if options.follow_symlinks {
517 tokio::fs::canonicalize(path).await?
518 } else {
519 path
520 };
521
522 let walk = WalkBuilder::new(path)
523 .follow_links(options.follow_symlinks)
524 .max_depth(if options.recursive { None } else { Some(0) })
525 .standard_filters(false)
526 .skip_stdout(true)
527 .build();
528
529 let mut errors = Vec::new();
531 for entry in walk {
532 match entry {
533 Ok(entry) if entry.path_is_symlink() && options.exclude_symlinks => {}
534 Ok(entry) => {
535 if let Err(x) = set_permissions_impl(&entry, &permissions).await {
536 errors.push(format!("{:?}: {x}", entry.path()));
537 }
538 }
539 Err(x) => {
540 errors.push(x.to_string());
541 }
542 }
543 }
544
545 if errors.is_empty() {
546 Ok(())
547 } else {
548 Err(io::Error::new(
549 io::ErrorKind::PermissionDenied,
550 errors
551 .into_iter()
552 .map(|x| format!("* {x}"))
553 .collect::<Vec<_>>()
554 .join("\n"),
555 ))
556 }
557 }
558
559 async fn search(&self, ctx: DistantCtx, query: SearchQuery) -> io::Result<SearchId> {
560 debug!(
561 "[Conn {}] Performing search via {query:?}",
562 ctx.connection_id,
563 );
564
565 self.state.search.start(query, ctx.reply).await
566 }
567
568 async fn cancel_search(&self, ctx: DistantCtx, id: SearchId) -> io::Result<()> {
569 debug!("[Conn {}] Cancelling search {id}", ctx.connection_id,);
570
571 self.state.search.cancel(id).await
572 }
573
574 async fn proc_spawn(
575 &self,
576 ctx: DistantCtx,
577 cmd: String,
578 environment: Environment,
579 current_dir: Option<PathBuf>,
580 pty: Option<PtySize>,
581 ) -> io::Result<ProcessId> {
582 debug!(
583 "[Conn {}] Spawning {} {{environment: {:?}, current_dir: {:?}, pty: {:?}}}",
584 ctx.connection_id, cmd, environment, current_dir, pty
585 );
586 self.state
587 .process
588 .spawn(cmd, environment, current_dir, pty, ctx.reply)
589 .await
590 }
591
592 async fn proc_kill(&self, ctx: DistantCtx, id: ProcessId) -> io::Result<()> {
593 debug!("[Conn {}] Killing process {}", ctx.connection_id, id);
594 self.state.process.kill(id).await
595 }
596
597 async fn proc_stdin(&self, ctx: DistantCtx, id: ProcessId, data: Vec<u8>) -> io::Result<()> {
598 debug!(
599 "[Conn {}] Sending stdin to process {}",
600 ctx.connection_id, id
601 );
602 self.state.process.send_stdin(id, data).await
603 }
604
605 async fn proc_resize_pty(
606 &self,
607 ctx: DistantCtx,
608 id: ProcessId,
609 size: PtySize,
610 ) -> io::Result<()> {
611 debug!(
612 "[Conn {}] Resizing pty of process {} to {}",
613 ctx.connection_id, id, size
614 );
615 self.state.process.resize_pty(id, size).await
616 }
617
618 async fn system_info(&self, ctx: DistantCtx) -> io::Result<SystemInfo> {
619 debug!("[Conn {}] Reading system information", ctx.connection_id);
620 Ok(SystemInfo {
621 family: env::consts::FAMILY.to_string(),
622 os: env::consts::OS.to_string(),
623 arch: env::consts::ARCH.to_string(),
624 current_dir: env::current_dir().unwrap_or_default(),
625 main_separator: std::path::MAIN_SEPARATOR,
626 username: whoami::username(),
627 shell: if cfg!(windows) {
628 env::var("ComSpec").unwrap_or_else(|_| String::from("cmd.exe"))
629 } else {
630 env::var("SHELL").unwrap_or_else(|_| String::from("/bin/sh"))
631 },
632 })
633 }
634
635 async fn version(&self, ctx: DistantCtx) -> io::Result<Version> {
636 debug!("[Conn {}] Querying version", ctx.connection_id);
637
638 let mut server_version: semver::Version = env!("CARGO_PKG_VERSION")
640 .parse()
641 .map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
642
643 if server_version.build.is_empty() {
645 server_version.build = semver::BuildMetadata::new(env!("CARGO_PKG_NAME"))
646 .map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
647 } else {
648 let raw_build_str = format!(
649 "{}.{}",
650 server_version.build.as_str(),
651 env!("CARGO_PKG_NAME")
652 );
653 server_version.build = semver::BuildMetadata::new(&raw_build_str)
654 .map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;
655 }
656
657 Ok(Version {
658 server_version,
659 protocol_version: PROTOCOL_VERSION,
660 capabilities: Version::capabilities()
661 .iter()
662 .map(ToString::to_string)
663 .collect(),
664 })
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use std::time::Duration;
671
672 use assert_fs::prelude::*;
673 use distant_core::net::server::Reply;
674 use distant_core::protocol::Response;
675 use once_cell::sync::Lazy;
676 use predicates::prelude::*;
677 use test_log::test;
678 use tokio::sync::mpsc;
679
680 use super::*;
681 use crate::config::WatchConfig;
682
683 static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> =
684 Lazy::new(|| assert_fs::TempDir::new().unwrap());
685 static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
686
687 static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
688 let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
689 script
690 .write_str(indoc::indoc!(
691 r#"
692 #/usr/bin/env bash
693 printf "%s" "$*"
694 "#
695 ))
696 .unwrap();
697 script
698 });
699
700 static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
701 let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
702 script
703 .write_str(indoc::indoc!(
704 r#"
705 #/usr/bin/env bash
706 printf "%s" "$*" 1>&2
707 "#
708 ))
709 .unwrap();
710 script
711 });
712
713 static ECHO_STDIN_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
714 let script = TEMP_SCRIPT_DIR.child("echo_stdin_to_stdout.sh");
715 script
716 .write_str(indoc::indoc!(
717 r#"
718 #/usr/bin/env bash
719 while IFS= read; do echo "$REPLY"; done
720 "#
721 ))
722 .unwrap();
723 script
724 });
725
726 static SLEEP_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
727 let script = TEMP_SCRIPT_DIR.child("sleep.sh");
728 script
729 .write_str(indoc::indoc!(
730 r#"
731 #!/usr/bin/env bash
732 sleep "$1"
733 "#
734 ))
735 .unwrap();
736 script
737 });
738
739 static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
740 Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
741
742 const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
743
744 async fn setup() -> (Api, DistantCtx, mpsc::UnboundedReceiver<Response>) {
745 let api = Api::initialize(Config {
746 watch: WatchConfig {
747 debounce_timeout: DEBOUNCE_TIMEOUT,
748 ..Default::default()
749 },
750 })
751 .unwrap();
752 let (reply, rx) = make_reply();
753 let connection_id = rand::random();
754
755 DistantApi::on_connect(&api, connection_id).await.unwrap();
756 let ctx = DistantCtx {
757 connection_id,
758 reply,
759 };
760 (api, ctx, rx)
761 }
762
763 fn make_reply() -> (
764 Box<dyn Reply<Data = Response>>,
765 mpsc::UnboundedReceiver<Response>,
766 ) {
767 let (tx, rx) = mpsc::unbounded_channel();
768 (Box::new(tx), rx)
769 }
770
771 #[test(tokio::test)]
772 async fn read_file_should_fail_if_file_missing() {
773 let (api, ctx, _rx) = setup().await;
774 let temp = assert_fs::TempDir::new().unwrap();
775 let path = temp.child("missing-file").path().to_path_buf();
776
777 let _ = api.read_file(ctx, path).await.unwrap_err();
778 }
779
780 #[test(tokio::test)]
781 async fn read_file_should_send_blob_with_file_contents() {
782 let (api, ctx, _rx) = setup().await;
783
784 let temp = assert_fs::TempDir::new().unwrap();
785 let file = temp.child("test-file");
786 file.write_str("some file contents").unwrap();
787
788 let bytes = api.read_file(ctx, file.path().to_path_buf()).await.unwrap();
789 assert_eq!(bytes, b"some file contents");
790 }
791
792 #[test(tokio::test)]
793 async fn read_file_text_should_send_error_if_fails_to_read_file() {
794 let (api, ctx, _rx) = setup().await;
795
796 let temp = assert_fs::TempDir::new().unwrap();
797 let path = temp.child("missing-file").path().to_path_buf();
798
799 let _ = api.read_file_text(ctx, path).await.unwrap_err();
800 }
801
802 #[test(tokio::test)]
803 async fn read_file_text_should_send_text_with_file_contents() {
804 let (api, ctx, _rx) = setup().await;
805
806 let temp = assert_fs::TempDir::new().unwrap();
807 let file = temp.child("test-file");
808 file.write_str("some file contents").unwrap();
809
810 let text = api
811 .read_file_text(ctx, file.path().to_path_buf())
812 .await
813 .unwrap();
814 assert_eq!(text, "some file contents");
815 }
816
817 #[test(tokio::test)]
818 async fn write_file_should_send_error_if_fails_to_write_file() {
819 let (api, ctx, _rx) = setup().await;
820
821 let temp = assert_fs::TempDir::new().unwrap();
824 let file = temp.child("dir").child("test-file");
825
826 let _ = api
827 .write_file(ctx, file.path().to_path_buf(), b"some text".to_vec())
828 .await
829 .unwrap_err();
830
831 file.assert(predicate::path::missing());
833 }
834
835 #[test(tokio::test)]
836 async fn write_file_should_send_ok_when_successful() {
837 let (api, ctx, _rx) = setup().await;
838
839 let temp = assert_fs::TempDir::new().unwrap();
842 let file = temp.child("test-file");
843
844 api.write_file(ctx, file.path().to_path_buf(), b"some text".to_vec())
845 .await
846 .unwrap();
847
848 file.assert("some text");
851 }
852
853 #[test(tokio::test)]
854 async fn write_file_text_should_send_error_if_fails_to_write_file() {
855 let (api, ctx, _rx) = setup().await;
856
857 let temp = assert_fs::TempDir::new().unwrap();
860 let file = temp.child("dir").child("test-file");
861
862 api.write_file_text(ctx, file.path().to_path_buf(), "some text".to_string())
863 .await
864 .unwrap_err();
865
866 file.assert(predicate::path::missing());
868 }
869
870 #[test(tokio::test)]
871 async fn write_file_text_should_send_ok_when_successful() {
872 let (api, ctx, _rx) = setup().await;
873
874 let temp = assert_fs::TempDir::new().unwrap();
877 let file = temp.child("test-file");
878
879 api.write_file_text(ctx, file.path().to_path_buf(), "some text".to_string())
880 .await
881 .unwrap();
882
883 file.assert("some text");
886 }
887
888 #[test(tokio::test)]
889 async fn append_file_should_send_error_if_fails_to_create_file() {
890 let (api, ctx, _rx) = setup().await;
891
892 let temp = assert_fs::TempDir::new().unwrap();
895 let file = temp.child("dir").child("test-file");
896
897 api.append_file(
898 ctx,
899 file.path().to_path_buf(),
900 b"some extra contents".to_vec(),
901 )
902 .await
903 .unwrap_err();
904
905 file.assert(predicate::path::missing());
907 }
908
909 #[test(tokio::test)]
910 async fn append_file_should_create_file_if_missing() {
911 let (api, ctx, _rx) = setup().await;
912
913 let temp = assert_fs::TempDir::new().unwrap();
916 let file = temp.child("test-file");
917
918 api.append_file(
919 ctx,
920 file.path().to_path_buf(),
921 b"some extra contents".to_vec(),
922 )
923 .await
924 .unwrap();
925
926 tokio::time::sleep(Duration::from_millis(50)).await;
928
929 file.assert("some extra contents");
931 }
932
933 #[test(tokio::test)]
934 async fn append_file_should_send_ok_when_successful() {
935 let (api, ctx, _rx) = setup().await;
936
937 let temp = assert_fs::TempDir::new().unwrap();
939 let file = temp.child("test-file");
940 file.write_str("some file contents").unwrap();
941
942 api.append_file(
943 ctx,
944 file.path().to_path_buf(),
945 b"some extra contents".to_vec(),
946 )
947 .await
948 .unwrap();
949
950 tokio::time::sleep(Duration::from_millis(50)).await;
952
953 file.assert("some file contentssome extra contents");
955 }
956
957 #[test(tokio::test)]
958 async fn append_file_text_should_send_error_if_fails_to_create_file() {
959 let (api, ctx, _rx) = setup().await;
960
961 let temp = assert_fs::TempDir::new().unwrap();
964 let file = temp.child("dir").child("test-file");
965
966 let _ = api
967 .append_file_text(
968 ctx,
969 file.path().to_path_buf(),
970 "some extra contents".to_string(),
971 )
972 .await
973 .unwrap_err();
974
975 file.assert(predicate::path::missing());
977 }
978
979 #[test(tokio::test)]
980 async fn append_file_text_should_create_file_if_missing() {
981 let (api, ctx, _rx) = setup().await;
982
983 let temp = assert_fs::TempDir::new().unwrap();
986 let file = temp.child("test-file");
987
988 api.append_file_text(
989 ctx,
990 file.path().to_path_buf(),
991 "some extra contents".to_string(),
992 )
993 .await
994 .unwrap();
995
996 tokio::time::sleep(Duration::from_millis(50)).await;
998
999 file.assert("some extra contents");
1001 }
1002
1003 #[test(tokio::test)]
1004 async fn append_file_text_should_send_ok_when_successful() {
1005 let (api, ctx, _rx) = setup().await;
1006
1007 let temp = assert_fs::TempDir::new().unwrap();
1009 let file = temp.child("test-file");
1010 file.write_str("some file contents").unwrap();
1011
1012 api.append_file_text(
1013 ctx,
1014 file.path().to_path_buf(),
1015 "some extra contents".to_string(),
1016 )
1017 .await
1018 .unwrap();
1019
1020 tokio::time::sleep(Duration::from_millis(50)).await;
1022
1023 file.assert("some file contentssome extra contents");
1025 }
1026
1027 #[test(tokio::test)]
1028 async fn dir_read_should_send_error_if_directory_does_not_exist() {
1029 let (api, ctx, _rx) = setup().await;
1030
1031 let temp = assert_fs::TempDir::new().unwrap();
1032 let dir = temp.child("test-dir");
1033
1034 let _ = api
1035 .read_dir(
1036 ctx,
1037 dir.path().to_path_buf(),
1038 0,
1039 false,
1040 false,
1041 false,
1042 )
1043 .await
1044 .unwrap_err();
1045 }
1046
1047 async fn setup_dir() -> assert_fs::TempDir {
1053 let root_dir = assert_fs::TempDir::new().unwrap();
1054 root_dir.child("file1").touch().unwrap();
1055
1056 let sub1 = root_dir.child("sub1");
1057 sub1.create_dir_all().unwrap();
1058
1059 let file2 = sub1.child("file2");
1060 file2.touch().unwrap();
1061
1062 let link1 = root_dir.child("link1");
1063 link1.symlink_to_file(file2.path()).unwrap();
1064
1065 root_dir
1066 }
1067
1068 #[test(tokio::test)]
1069 async fn dir_read_should_support_depth_limits() {
1070 let (api, ctx, _rx) = setup().await;
1071
1072 let root_dir = setup_dir().await;
1074
1075 let (entries, _) = api
1076 .read_dir(
1077 ctx,
1078 root_dir.path().to_path_buf(),
1079 1,
1080 false,
1081 false,
1082 false,
1083 )
1084 .await
1085 .unwrap();
1086
1087 assert_eq!(entries.len(), 3, "Wrong number of entries found");
1088
1089 assert_eq!(entries[0].file_type, FileType::File);
1090 assert_eq!(entries[0].path, Path::new("file1"));
1091 assert_eq!(entries[0].depth, 1);
1092
1093 assert_eq!(entries[1].file_type, FileType::Symlink);
1094 assert_eq!(entries[1].path, Path::new("link1"));
1095 assert_eq!(entries[1].depth, 1);
1096
1097 assert_eq!(entries[2].file_type, FileType::Dir);
1098 assert_eq!(entries[2].path, Path::new("sub1"));
1099 assert_eq!(entries[2].depth, 1);
1100 }
1101
1102 #[test(tokio::test)]
1103 async fn dir_read_should_support_unlimited_depth_using_zero() {
1104 let (api, ctx, _rx) = setup().await;
1105
1106 let root_dir = setup_dir().await;
1108
1109 let (entries, _) = api
1110 .read_dir(
1111 ctx,
1112 root_dir.path().to_path_buf(),
1113 0,
1114 false,
1115 false,
1116 false,
1117 )
1118 .await
1119 .unwrap();
1120
1121 assert_eq!(entries.len(), 4, "Wrong number of entries found");
1122
1123 assert_eq!(entries[0].file_type, FileType::File);
1124 assert_eq!(entries[0].path, Path::new("file1"));
1125 assert_eq!(entries[0].depth, 1);
1126
1127 assert_eq!(entries[1].file_type, FileType::Symlink);
1128 assert_eq!(entries[1].path, Path::new("link1"));
1129 assert_eq!(entries[1].depth, 1);
1130
1131 assert_eq!(entries[2].file_type, FileType::Dir);
1132 assert_eq!(entries[2].path, Path::new("sub1"));
1133 assert_eq!(entries[2].depth, 1);
1134
1135 assert_eq!(entries[3].file_type, FileType::File);
1136 assert_eq!(entries[3].path, Path::new("sub1").join("file2"));
1137 assert_eq!(entries[3].depth, 2);
1138 }
1139
1140 #[test(tokio::test)]
1141 async fn dir_read_should_support_including_directory_in_returned_entries() {
1142 let (api, ctx, _rx) = setup().await;
1143
1144 let root_dir = setup_dir().await;
1146
1147 let (entries, _) = api
1148 .read_dir(
1149 ctx,
1150 root_dir.path().to_path_buf(),
1151 1,
1152 false,
1153 false,
1154 true,
1155 )
1156 .await
1157 .unwrap();
1158
1159 assert_eq!(entries.len(), 4, "Wrong number of entries found");
1160
1161 assert_eq!(entries[0].file_type, FileType::Dir);
1163 assert_eq!(entries[0].path, root_dir.path().canonicalize().unwrap());
1164 assert_eq!(entries[0].depth, 0);
1165
1166 assert_eq!(entries[1].file_type, FileType::File);
1167 assert_eq!(entries[1].path, Path::new("file1"));
1168 assert_eq!(entries[1].depth, 1);
1169
1170 assert_eq!(entries[2].file_type, FileType::Symlink);
1171 assert_eq!(entries[2].path, Path::new("link1"));
1172 assert_eq!(entries[2].depth, 1);
1173
1174 assert_eq!(entries[3].file_type, FileType::Dir);
1175 assert_eq!(entries[3].path, Path::new("sub1"));
1176 assert_eq!(entries[3].depth, 1);
1177 }
1178
1179 #[test(tokio::test)]
1180 async fn dir_read_should_support_returning_absolute_paths() {
1181 let (api, ctx, _rx) = setup().await;
1182
1183 let root_dir = setup_dir().await;
1185
1186 let (entries, _) = api
1187 .read_dir(
1188 ctx,
1189 root_dir.path().to_path_buf(),
1190 1,
1191 true,
1192 false,
1193 false,
1194 )
1195 .await
1196 .unwrap();
1197
1198 assert_eq!(entries.len(), 3, "Wrong number of entries found");
1199 let root_path = root_dir.path().canonicalize().unwrap();
1200
1201 assert_eq!(entries[0].file_type, FileType::File);
1202 assert_eq!(entries[0].path, root_path.join("file1"));
1203 assert_eq!(entries[0].depth, 1);
1204
1205 assert_eq!(entries[1].file_type, FileType::Symlink);
1206 assert_eq!(entries[1].path, root_path.join("link1"));
1207 assert_eq!(entries[1].depth, 1);
1208
1209 assert_eq!(entries[2].file_type, FileType::Dir);
1210 assert_eq!(entries[2].path, root_path.join("sub1"));
1211 assert_eq!(entries[2].depth, 1);
1212 }
1213
1214 #[test(tokio::test)]
1215 async fn dir_read_should_support_returning_canonicalized_paths() {
1216 let (api, ctx, _rx) = setup().await;
1217
1218 let root_dir = setup_dir().await;
1220
1221 let (entries, _) = api
1222 .read_dir(
1223 ctx,
1224 root_dir.path().to_path_buf(),
1225 1,
1226 false,
1227 true,
1228 false,
1229 )
1230 .await
1231 .unwrap();
1232
1233 assert_eq!(entries.len(), 3, "Wrong number of entries found");
1234
1235 assert_eq!(entries[0].file_type, FileType::File);
1236 assert_eq!(entries[0].path, Path::new("file1"));
1237 assert_eq!(entries[0].depth, 1);
1238
1239 assert_eq!(entries[1].file_type, FileType::Symlink);
1241 assert_eq!(entries[1].path, Path::new("sub1").join("file2"));
1242 assert_eq!(entries[1].depth, 1);
1243
1244 assert_eq!(entries[2].file_type, FileType::Dir);
1245 assert_eq!(entries[2].path, Path::new("sub1"));
1246 assert_eq!(entries[2].depth, 1);
1247 }
1248
1249 #[test(tokio::test)]
1250 async fn create_dir_should_send_error_if_fails() {
1251 let (api, ctx, _rx) = setup().await;
1252
1253 let root_dir = setup_dir().await;
1256 let path = root_dir.path().join("nested").join("new-dir");
1257
1258 let _ = api
1259 .create_dir(ctx, path.to_path_buf(), false)
1260 .await
1261 .unwrap_err();
1262
1263 assert!(!path.exists(), "Path unexpectedly exists");
1265 }
1266
1267 #[test(tokio::test)]
1268 async fn create_dir_should_send_ok_when_successful() {
1269 let (api, ctx, _rx) = setup().await;
1270 let root_dir = setup_dir().await;
1271 let path = root_dir.path().join("new-dir");
1272
1273 api.create_dir(ctx, path.to_path_buf(), false)
1274 .await
1275 .unwrap();
1276
1277 assert!(path.exists(), "Directory not created");
1279 }
1280
1281 #[test(tokio::test)]
1282 async fn create_dir_should_support_creating_multiple_dir_components() {
1283 let (api, ctx, _rx) = setup().await;
1284 let root_dir = setup_dir().await;
1285 let path = root_dir.path().join("nested").join("new-dir");
1286
1287 api.create_dir(ctx, path.to_path_buf(), true)
1288 .await
1289 .unwrap();
1290
1291 assert!(path.exists(), "Directory not created");
1293 }
1294
1295 #[test(tokio::test)]
1296 async fn remove_should_send_error_on_failure() {
1297 let (api, ctx, _rx) = setup().await;
1298 let temp = assert_fs::TempDir::new().unwrap();
1299 let file = temp.child("missing-file");
1300
1301 let _ = api
1302 .remove(ctx, file.path().to_path_buf(), false)
1303 .await
1304 .unwrap_err();
1305
1306 file.assert(predicate::path::missing());
1308 }
1309
1310 #[test(tokio::test)]
1311 async fn remove_should_support_deleting_a_directory() {
1312 let (api, ctx, _rx) = setup().await;
1313 let temp = assert_fs::TempDir::new().unwrap();
1314 let dir = temp.child("dir");
1315 dir.create_dir_all().unwrap();
1316
1317 api.remove(ctx, dir.path().to_path_buf(), false)
1318 .await
1319 .unwrap();
1320
1321 dir.assert(predicate::path::missing());
1323 }
1324
1325 #[test(tokio::test)]
1326 async fn remove_should_delete_nonempty_directory_if_force_is_true() {
1327 let (api, ctx, _rx) = setup().await;
1328 let temp = assert_fs::TempDir::new().unwrap();
1329 let dir = temp.child("dir");
1330 dir.create_dir_all().unwrap();
1331 dir.child("file").touch().unwrap();
1332
1333 api.remove(ctx, dir.path().to_path_buf(), true)
1334 .await
1335 .unwrap();
1336
1337 dir.assert(predicate::path::missing());
1339 }
1340
1341 #[test(tokio::test)]
1342 async fn remove_should_support_deleting_a_single_file() {
1343 let (api, ctx, _rx) = setup().await;
1344 let temp = assert_fs::TempDir::new().unwrap();
1345 let file = temp.child("some-file");
1346 file.touch().unwrap();
1347
1348 api.remove(ctx, file.path().to_path_buf(), false)
1349 .await
1350 .unwrap();
1351
1352 file.assert(predicate::path::missing());
1354 }
1355
1356 #[test(tokio::test)]
1357 async fn copy_should_send_error_on_failure() {
1358 let (api, ctx, _rx) = setup().await;
1359 let temp = assert_fs::TempDir::new().unwrap();
1360 let src = temp.child("src");
1361 let dst = temp.child("dst");
1362
1363 let _ = api
1364 .copy(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1365 .await
1366 .unwrap_err();
1367
1368 dst.assert(predicate::path::missing());
1370 }
1371
1372 #[test(tokio::test)]
1373 async fn copy_should_support_copying_an_entire_directory() {
1374 let (api, ctx, _rx) = setup().await;
1375 let temp = assert_fs::TempDir::new().unwrap();
1376
1377 let src = temp.child("src");
1378 src.create_dir_all().unwrap();
1379 let src_file = src.child("file");
1380 src_file.write_str("some contents").unwrap();
1381
1382 let dst = temp.child("dst");
1383 let dst_file = dst.child("file");
1384
1385 api.copy(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1386 .await
1387 .unwrap();
1388
1389 src.assert(predicate::path::is_dir());
1391 src_file.assert(predicate::path::is_file());
1392 dst.assert(predicate::path::is_dir());
1393 dst_file.assert(predicate::path::eq_file(src_file.path()));
1394 }
1395
1396 #[test(tokio::test)]
1397 async fn copy_should_support_copying_an_empty_directory() {
1398 let (api, ctx, _rx) = setup().await;
1399 let temp = assert_fs::TempDir::new().unwrap();
1400 let src = temp.child("src");
1401 src.create_dir_all().unwrap();
1402 let dst = temp.child("dst");
1403
1404 api.copy(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1405 .await
1406 .unwrap();
1407
1408 src.assert(predicate::path::is_dir());
1410 dst.assert(predicate::path::is_dir());
1411 }
1412
1413 #[test(tokio::test)]
1414 async fn copy_should_support_copying_a_directory_that_only_contains_directories() {
1415 let (api, ctx, _rx) = setup().await;
1416 let temp = assert_fs::TempDir::new().unwrap();
1417
1418 let src = temp.child("src");
1419 src.create_dir_all().unwrap();
1420 let src_dir = src.child("dir");
1421 src_dir.create_dir_all().unwrap();
1422
1423 let dst = temp.child("dst");
1424 let dst_dir = dst.child("dir");
1425
1426 api.copy(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1427 .await
1428 .unwrap();
1429
1430 src.assert(predicate::path::is_dir().name("src"));
1432 src_dir.assert(predicate::path::is_dir().name("src/dir"));
1433 dst.assert(predicate::path::is_dir().name("dst"));
1434 dst_dir.assert(predicate::path::is_dir().name("dst/dir"));
1435 }
1436
1437 #[test(tokio::test)]
1438 async fn copy_should_support_copying_a_single_file() {
1439 let (api, ctx, _rx) = setup().await;
1440 let temp = assert_fs::TempDir::new().unwrap();
1441 let src = temp.child("src");
1442 src.write_str("some text").unwrap();
1443 let dst = temp.child("dst");
1444
1445 api.copy(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1446 .await
1447 .unwrap();
1448
1449 src.assert(predicate::path::is_file());
1451 dst.assert(predicate::path::eq_file(src.path()));
1452 }
1453
1454 #[test(tokio::test)]
1455 async fn rename_should_fail_if_path_missing() {
1456 let (api, ctx, _rx) = setup().await;
1457 let temp = assert_fs::TempDir::new().unwrap();
1458 let src = temp.child("src");
1459 let dst = temp.child("dst");
1460
1461 let _ = api
1462 .rename(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1463 .await
1464 .unwrap_err();
1465
1466 dst.assert(predicate::path::missing());
1468 }
1469
1470 #[test(tokio::test)]
1471 async fn rename_should_support_renaming_an_entire_directory() {
1472 let (api, ctx, _rx) = setup().await;
1473 let temp = assert_fs::TempDir::new().unwrap();
1474
1475 let src = temp.child("src");
1476 src.create_dir_all().unwrap();
1477 let src_file = src.child("file");
1478 src_file.write_str("some contents").unwrap();
1479
1480 let dst = temp.child("dst");
1481 let dst_file = dst.child("file");
1482
1483 api.rename(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1484 .await
1485 .unwrap();
1486
1487 src.assert(predicate::path::missing());
1489 src_file.assert(predicate::path::missing());
1490 dst.assert(predicate::path::is_dir());
1491 dst_file.assert("some contents");
1492 }
1493
1494 #[test(tokio::test)]
1495 async fn rename_should_support_renaming_a_single_file() {
1496 let (api, ctx, _rx) = setup().await;
1497 let temp = assert_fs::TempDir::new().unwrap();
1498 let src = temp.child("src");
1499 src.write_str("some text").unwrap();
1500 let dst = temp.child("dst");
1501
1502 api.rename(ctx, src.path().to_path_buf(), dst.path().to_path_buf())
1503 .await
1504 .unwrap();
1505
1506 src.assert(predicate::path::missing());
1508 dst.assert("some text");
1509 }
1510
1511 fn validate_changed_path(data: &Response, expected_path: &Path, should_panic: bool) -> bool {
1513 match data {
1514 Response::Changed(change) if should_panic => {
1515 let path = change.path.canonicalize().unwrap();
1516 assert_eq!(path, expected_path, "Wrong path reported: {:?}", change);
1517
1518 true
1519 }
1520 Response::Changed(change) => {
1521 let path = change.path.canonicalize().unwrap();
1522 path == expected_path
1523 }
1524 x if should_panic => panic!("Unexpected response: {:?}", x),
1525 _ => false,
1526 }
1527 }
1528
1529 #[test(tokio::test)]
1530 async fn watch_should_support_watching_a_single_file() {
1531 let (api, ctx, mut rx) = setup().await;
1533 let temp = assert_fs::TempDir::new().unwrap();
1534
1535 let file = temp.child("file");
1536 file.touch().unwrap();
1537
1538 api.watch(
1539 ctx,
1540 file.path().to_path_buf(),
1541 false,
1542 Default::default(),
1543 Default::default(),
1544 )
1545 .await
1546 .unwrap();
1547
1548 file.write_str("some text").unwrap();
1550
1551 let data = rx
1552 .recv()
1553 .await
1554 .expect("Channel closed before we got change");
1555 validate_changed_path(
1556 &data,
1557 &file.path().to_path_buf().canonicalize().unwrap(),
1558 true,
1559 );
1560 }
1561
1562 #[test(tokio::test)]
1563 async fn watch_should_support_watching_a_directory_recursively() {
1564 let (api, ctx, mut rx) = setup().await;
1566 let temp = assert_fs::TempDir::new().unwrap();
1567
1568 let file = temp.child("file");
1569 file.touch().unwrap();
1570
1571 let dir = temp.child("dir");
1572 dir.create_dir_all().unwrap();
1573
1574 api.watch(
1575 ctx,
1576 temp.path().to_path_buf(),
1577 true,
1578 Default::default(),
1579 Default::default(),
1580 )
1581 .await
1582 .unwrap();
1583
1584 file.write_str("some text").unwrap();
1586
1587 let nested_file = dir.child("nested-file");
1589 nested_file.write_str("some text").unwrap();
1590
1591 tokio::time::sleep(DEBOUNCE_TIMEOUT + Duration::from_millis(100)).await;
1594
1595 let mut responses = Vec::new();
1597 while let Ok(res) = rx.try_recv() {
1598 responses.push(res);
1599 }
1600
1601 assert!(
1603 responses.len() >= 2,
1604 "Less than expected total responses: {:?}",
1605 responses
1606 );
1607
1608 let path = file.path().to_path_buf();
1609 assert!(
1610 responses.iter().any(|res| validate_changed_path(
1611 res,
1612 &file.path().to_path_buf().canonicalize().unwrap(),
1613 false,
1614 )),
1615 "Missing {:?} in {:?}",
1616 path,
1617 responses
1618 .iter()
1619 .map(|x| format!("{:?}", x))
1620 .collect::<Vec<String>>(),
1621 );
1622
1623 let path = nested_file.path().to_path_buf();
1624 assert!(
1625 responses.iter().any(|res| validate_changed_path(
1626 res,
1627 &file.path().to_path_buf().canonicalize().unwrap(),
1628 false,
1629 )),
1630 "Missing {:?} in {:?}",
1631 path,
1632 responses
1633 .iter()
1634 .map(|x| format!("{:?}", x))
1635 .collect::<Vec<String>>(),
1636 );
1637 }
1638
1639 #[test(tokio::test)]
1640 async fn watch_should_report_changes_using_the_ctx_replies() {
1641 let (api, ctx_1, mut rx_1) = setup().await;
1643 let (ctx_2, mut rx_2) = {
1644 let (reply, rx) = make_reply();
1645 let ctx = DistantCtx {
1646 connection_id: ctx_1.connection_id,
1647 reply,
1648 };
1649 (ctx, rx)
1650 };
1651
1652 let temp = assert_fs::TempDir::new().unwrap();
1653
1654 let file_1 = temp.child("file_1");
1655 file_1.touch().unwrap();
1656
1657 let file_2 = temp.child("file_2");
1658 file_2.touch().unwrap();
1659
1660 tokio::time::sleep(Duration::from_millis(100)).await;
1663
1664 api.watch(
1666 ctx_1,
1667 file_1.path().to_path_buf(),
1668 false,
1669 Default::default(),
1670 Default::default(),
1671 )
1672 .await
1673 .unwrap();
1674
1675 api.watch(
1677 ctx_2,
1678 file_2.path().to_path_buf(),
1679 false,
1680 Default::default(),
1681 Default::default(),
1682 )
1683 .await
1684 .unwrap();
1685
1686 file_1.write_str("some text").unwrap();
1688 let data = rx_1
1689 .recv()
1690 .await
1691 .expect("Channel closed before we got change");
1692 validate_changed_path(
1693 &data,
1694 &file_1.path().to_path_buf().canonicalize().unwrap(),
1695 true,
1696 );
1697
1698 file_2.write_str("some text").unwrap();
1700 let data = rx_2
1701 .recv()
1702 .await
1703 .expect("Channel closed before we got change");
1704 validate_changed_path(
1705 &data,
1706 &file_2.path().to_path_buf().canonicalize().unwrap(),
1707 true,
1708 );
1709 }
1710
1711 #[test(tokio::test)]
1712 async fn exists_should_send_true_if_path_exists() {
1713 let (api, ctx, _rx) = setup().await;
1714 let temp = assert_fs::TempDir::new().unwrap();
1715 let file = temp.child("file");
1716 file.touch().unwrap();
1717
1718 let exists = api.exists(ctx, file.path().to_path_buf()).await.unwrap();
1719 assert!(exists, "Expected exists to be true, but was false");
1720 }
1721
1722 #[test(tokio::test)]
1723 async fn exists_should_send_false_if_path_does_not_exist() {
1724 let (api, ctx, _rx) = setup().await;
1725 let temp = assert_fs::TempDir::new().unwrap();
1726 let file = temp.child("file");
1727
1728 let exists = api.exists(ctx, file.path().to_path_buf()).await.unwrap();
1729 assert!(!exists, "Expected exists to be false, but was true");
1730 }
1731
1732 #[test(tokio::test)]
1733 async fn metadata_should_send_error_on_failure() {
1734 let (api, ctx, _rx) = setup().await;
1735 let temp = assert_fs::TempDir::new().unwrap();
1736 let file = temp.child("file");
1737
1738 let _ = api
1739 .metadata(
1740 ctx,
1741 file.path().to_path_buf(),
1742 false,
1743 false,
1744 )
1745 .await
1746 .unwrap_err();
1747 }
1748
1749 #[test(tokio::test)]
1750 async fn metadata_should_send_back_metadata_on_file_if_exists() {
1751 let (api, ctx, _rx) = setup().await;
1752 let temp = assert_fs::TempDir::new().unwrap();
1753 let file = temp.child("file");
1754 file.write_str("some text").unwrap();
1755
1756 let metadata = api
1757 .metadata(
1758 ctx,
1759 file.path().to_path_buf(),
1760 false,
1761 false,
1762 )
1763 .await
1764 .unwrap();
1765
1766 assert!(
1767 matches!(
1768 metadata,
1769 Metadata {
1770 canonicalized_path: None,
1771 file_type: FileType::File,
1772 len: 9,
1773 readonly: false,
1774 ..
1775 }
1776 ),
1777 "{:?}",
1778 metadata
1779 );
1780 }
1781
1782 #[cfg(unix)]
1783 #[test(tokio::test)]
1784 async fn metadata_should_include_unix_specific_metadata_on_unix_platform() {
1785 let (api, ctx, _rx) = setup().await;
1786 let temp = assert_fs::TempDir::new().unwrap();
1787 let file = temp.child("file");
1788 file.write_str("some text").unwrap();
1789
1790 let metadata = api
1791 .metadata(
1792 ctx,
1793 file.path().to_path_buf(),
1794 false,
1795 false,
1796 )
1797 .await
1798 .unwrap();
1799
1800 #[allow(clippy::match_single_binding)]
1801 match metadata {
1802 Metadata { unix, windows, .. } => {
1803 assert!(unix.is_some(), "Unexpectedly missing unix metadata on unix");
1804 assert!(
1805 windows.is_none(),
1806 "Unexpectedly got windows metadata on unix"
1807 );
1808 }
1809 }
1810 }
1811
1812 #[cfg(windows)]
1813 #[test(tokio::test)]
1814 async fn metadata_should_include_windows_specific_metadata_on_windows_platform() {
1815 let (api, ctx, _rx) = setup().await;
1816 let temp = assert_fs::TempDir::new().unwrap();
1817 let file = temp.child("file");
1818 file.write_str("some text").unwrap();
1819
1820 let metadata = api
1821 .metadata(
1822 ctx,
1823 file.path().to_path_buf(),
1824 false,
1825 false,
1826 )
1827 .await
1828 .unwrap();
1829
1830 #[allow(clippy::match_single_binding)]
1831 match metadata {
1832 Metadata { unix, windows, .. } => {
1833 assert!(
1834 windows.is_some(),
1835 "Unexpectedly missing windows metadata on windows"
1836 );
1837 assert!(unix.is_none(), "Unexpectedly got unix metadata on windows");
1838 }
1839 }
1840 }
1841
1842 #[test(tokio::test)]
1843 async fn metadata_should_send_back_metadata_on_dir_if_exists() {
1844 let (api, ctx, _rx) = setup().await;
1845 let temp = assert_fs::TempDir::new().unwrap();
1846 let dir = temp.child("dir");
1847 dir.create_dir_all().unwrap();
1848
1849 let metadata = api
1850 .metadata(
1851 ctx,
1852 dir.path().to_path_buf(),
1853 false,
1854 false,
1855 )
1856 .await
1857 .unwrap();
1858
1859 assert!(
1860 matches!(
1861 metadata,
1862 Metadata {
1863 canonicalized_path: None,
1864 file_type: FileType::Dir,
1865 readonly: false,
1866 ..
1867 }
1868 ),
1869 "{:?}",
1870 metadata
1871 );
1872 }
1873
1874 #[test(tokio::test)]
1875 async fn metadata_should_send_back_metadata_on_symlink_if_exists() {
1876 let (api, ctx, _rx) = setup().await;
1877 let temp = assert_fs::TempDir::new().unwrap();
1878 let file = temp.child("file");
1879 file.write_str("some text").unwrap();
1880
1881 let symlink = temp.child("link");
1882 symlink.symlink_to_file(file.path()).unwrap();
1883
1884 let metadata = api
1885 .metadata(
1886 ctx,
1887 symlink.path().to_path_buf(),
1888 false,
1889 false,
1890 )
1891 .await
1892 .unwrap();
1893
1894 assert!(
1895 matches!(
1896 metadata,
1897 Metadata {
1898 canonicalized_path: None,
1899 file_type: FileType::Symlink,
1900 readonly: false,
1901 ..
1902 }
1903 ),
1904 "{:?}",
1905 metadata
1906 );
1907 }
1908
1909 #[test(tokio::test)]
1910 async fn metadata_should_include_canonicalized_path_if_flag_specified() {
1911 let (api, ctx, _rx) = setup().await;
1912 let temp = assert_fs::TempDir::new().unwrap();
1913 let file = temp.child("file");
1914 file.write_str("some text").unwrap();
1915
1916 let symlink = temp.child("link");
1917 symlink.symlink_to_file(file.path()).unwrap();
1918
1919 let metadata = api
1920 .metadata(
1921 ctx,
1922 symlink.path().to_path_buf(),
1923 true,
1924 false,
1925 )
1926 .await
1927 .unwrap();
1928
1929 match metadata {
1930 Metadata {
1931 canonicalized_path: Some(path),
1932 file_type: FileType::Symlink,
1933 readonly: false,
1934 ..
1935 } => assert_eq!(
1936 path,
1937 file.path().canonicalize().unwrap(),
1938 "Symlink canonicalized path does not match referenced file"
1939 ),
1940 x => panic!("Unexpected response: {:?}", x),
1941 }
1942 }
1943
1944 #[test(tokio::test)]
1945 async fn metadata_should_resolve_file_type_of_symlink_if_flag_specified() {
1946 let (api, ctx, _rx) = setup().await;
1947 let temp = assert_fs::TempDir::new().unwrap();
1948 let file = temp.child("file");
1949 file.write_str("some text").unwrap();
1950
1951 let symlink = temp.child("link");
1952 symlink.symlink_to_file(file.path()).unwrap();
1953
1954 let metadata = api
1955 .metadata(
1956 ctx,
1957 symlink.path().to_path_buf(),
1958 false,
1959 true,
1960 )
1961 .await
1962 .unwrap();
1963
1964 assert!(
1965 matches!(
1966 metadata,
1967 Metadata {
1968 file_type: FileType::File,
1969 ..
1970 }
1971 ),
1972 "{:?}",
1973 metadata
1974 );
1975 }
1976
1977 #[test(tokio::test)]
1978 async fn set_permissions_should_set_readonly_flag_if_specified() {
1979 let (api, ctx, _rx) = setup().await;
1980 let temp = assert_fs::TempDir::new().unwrap();
1981 let file = temp.child("file");
1982 file.write_str("some text").unwrap();
1983
1984 let permissions = tokio::fs::symlink_metadata(file.path())
1986 .await
1987 .unwrap()
1988 .permissions();
1989 assert!(!permissions.readonly(), "File is already set to readonly");
1990
1991 api.set_permissions(
1993 ctx,
1994 file.path().to_path_buf(),
1995 Permissions::readonly(),
1996 Default::default(),
1997 )
1998 .await
1999 .unwrap();
2000
2001 let permissions = tokio::fs::symlink_metadata(file.path())
2003 .await
2004 .unwrap()
2005 .permissions();
2006 assert!(permissions.readonly(), "File not set to readonly");
2007 }
2008
2009 #[test(tokio::test)]
2010 #[cfg_attr(not(unix), ignore)]
2011 async fn set_permissions_should_set_unix_permissions_if_on_unix_platform() {
2012 #[cfg(unix)]
2013 {
2014 use std::os::unix::prelude::*;
2015
2016 let (api, ctx, _rx) = setup().await;
2017 let temp = assert_fs::TempDir::new().unwrap();
2018 let file = temp.child("file");
2019 file.write_str("some text").unwrap();
2020
2021 let permissions = tokio::fs::symlink_metadata(file.path())
2023 .await
2024 .unwrap()
2025 .permissions();
2026 let mode = permissions.mode() & 0o777;
2027 assert_ne!(mode, 0o400, "File is already set to 0o400");
2028
2029 api.set_permissions(
2031 ctx,
2032 file.path().to_path_buf(),
2033 Permissions::from_unix_mode(0o400),
2034 Default::default(),
2035 )
2036 .await
2037 .unwrap();
2038
2039 let permissions = tokio::fs::symlink_metadata(file.path())
2041 .await
2042 .unwrap()
2043 .permissions();
2044
2045 let mode = permissions.mode() & 0o777;
2047
2048 assert_eq!(mode, 0o400, "Wrong permissions on file: {:o}", mode);
2049 }
2050 #[cfg(not(unix))]
2051 {
2052 unreachable!();
2053 }
2054 }
2055
2056 #[test(tokio::test)]
2057 #[cfg_attr(unix, ignore)]
2058 async fn set_permissions_should_set_readonly_flag_if_not_on_unix_platform() {
2059 let (api, ctx, _rx) = setup().await;
2060 let temp = assert_fs::TempDir::new().unwrap();
2061 let file = temp.child("file");
2062 file.write_str("some text").unwrap();
2063
2064 let permissions = tokio::fs::symlink_metadata(file.path())
2066 .await
2067 .unwrap()
2068 .permissions();
2069 assert!(!permissions.readonly(), "File is already set to readonly");
2070
2071 api.set_permissions(
2073 ctx,
2074 file.path().to_path_buf(),
2075 Permissions::from_unix_mode(0o400),
2076 Default::default(),
2077 )
2078 .await
2079 .unwrap();
2080
2081 #[cfg(not(unix))]
2082 {
2083 let permissions = tokio::fs::symlink_metadata(file.path())
2085 .await
2086 .unwrap()
2087 .permissions();
2088
2089 assert!(permissions.readonly(), "File not marked as readonly");
2090 }
2091 #[cfg(unix)]
2092 {
2093 unreachable!();
2094 }
2095 }
2096
2097 #[test(tokio::test)]
2098 async fn set_permissions_should_not_recurse_if_option_false() {
2099 let (api, ctx, _rx) = setup().await;
2100 let temp = assert_fs::TempDir::new().unwrap();
2101 let file = temp.child("file");
2102 file.write_str("some text").unwrap();
2103
2104 let symlink = temp.child("link");
2105 symlink.symlink_to_file(file.path()).unwrap();
2106
2107 let permissions = tokio::fs::symlink_metadata(temp.path())
2109 .await
2110 .unwrap()
2111 .permissions();
2112 assert!(
2113 !permissions.readonly(),
2114 "Temp dir is already set to readonly"
2115 );
2116
2117 let permissions = tokio::fs::symlink_metadata(file.path())
2119 .await
2120 .unwrap()
2121 .permissions();
2122 assert!(!permissions.readonly(), "File is already set to readonly");
2123
2124 let permissions = tokio::fs::symlink_metadata(symlink.path())
2126 .await
2127 .unwrap()
2128 .permissions();
2129 assert!(
2130 !permissions.readonly(),
2131 "Symlink is already set to readonly"
2132 );
2133
2134 api.set_permissions(
2136 ctx,
2137 temp.path().to_path_buf(),
2138 Permissions::readonly(),
2139 SetPermissionsOptions {
2140 recursive: false,
2141 ..Default::default()
2142 },
2143 )
2144 .await
2145 .unwrap();
2146
2147 let permissions = tokio::fs::symlink_metadata(temp.path())
2149 .await
2150 .unwrap()
2151 .permissions();
2152 assert!(permissions.readonly(), "Temp directory not set to readonly");
2153
2154 let permissions = tokio::fs::symlink_metadata(file.path())
2155 .await
2156 .unwrap()
2157 .permissions();
2158 assert!(!permissions.readonly(), "File unexpectedly set to readonly");
2159
2160 let permissions = tokio::fs::symlink_metadata(symlink.path())
2161 .await
2162 .unwrap()
2163 .permissions();
2164 assert!(
2165 !permissions.readonly(),
2166 "Symlink unexpectedly set to readonly"
2167 );
2168 }
2169
2170 #[test(tokio::test)]
2171 async fn set_permissions_should_traverse_symlinks_while_recursing_if_following_symlinks_enabled(
2172 ) {
2173 let (api, ctx, _rx) = setup().await;
2174 let temp = assert_fs::TempDir::new().unwrap();
2175 let file = temp.child("file");
2176 file.write_str("some text").unwrap();
2177
2178 let temp2 = assert_fs::TempDir::new().unwrap();
2179 let file2 = temp2.child("file");
2180 file2.write_str("some text").unwrap();
2181
2182 let symlink = temp.child("link");
2183 symlink.symlink_to_dir(temp2.path()).unwrap();
2184
2185 let permissions = tokio::fs::symlink_metadata(file2.path())
2187 .await
2188 .unwrap()
2189 .permissions();
2190 assert!(!permissions.readonly(), "File2 is already set to readonly");
2191
2192 api.set_permissions(
2194 ctx,
2195 temp.path().to_path_buf(),
2196 Permissions::readonly(),
2197 SetPermissionsOptions {
2198 follow_symlinks: true,
2199 recursive: true,
2200 ..Default::default()
2201 },
2202 )
2203 .await
2204 .unwrap();
2205
2206 let permissions = tokio::fs::symlink_metadata(file2.path())
2208 .await
2209 .unwrap()
2210 .permissions();
2211 assert!(permissions.readonly(), "File2 not set to readonly");
2212 }
2213
2214 #[test(tokio::test)]
2215 async fn set_permissions_should_not_traverse_symlinks_while_recursing_if_following_symlinks_disabled(
2216 ) {
2217 let (api, ctx, _rx) = setup().await;
2218 let temp = assert_fs::TempDir::new().unwrap();
2219 let file = temp.child("file");
2220 file.write_str("some text").unwrap();
2221
2222 let temp2 = assert_fs::TempDir::new().unwrap();
2223 let file2 = temp2.child("file");
2224 file2.write_str("some text").unwrap();
2225
2226 let symlink = temp.child("link");
2227 symlink.symlink_to_dir(temp2.path()).unwrap();
2228
2229 let permissions = tokio::fs::symlink_metadata(file2.path())
2231 .await
2232 .unwrap()
2233 .permissions();
2234 assert!(!permissions.readonly(), "File2 is already set to readonly");
2235
2236 api.set_permissions(
2238 ctx,
2239 temp.path().to_path_buf(),
2240 Permissions::readonly(),
2241 SetPermissionsOptions {
2242 follow_symlinks: false,
2243 recursive: true,
2244 ..Default::default()
2245 },
2246 )
2247 .await
2248 .unwrap();
2249
2250 let permissions = tokio::fs::symlink_metadata(file2.path())
2252 .await
2253 .unwrap()
2254 .permissions();
2255 assert!(
2256 !permissions.readonly(),
2257 "File2 unexpectedly set to readonly"
2258 );
2259 }
2260
2261 #[test(tokio::test)]
2262 async fn set_permissions_should_skip_symlinks_if_exclude_symlinks_enabled() {
2263 let (api, ctx, _rx) = setup().await;
2264 let temp = assert_fs::TempDir::new().unwrap();
2265 let file = temp.child("file");
2266 file.write_str("some text").unwrap();
2267
2268 let symlink = temp.child("link");
2269 symlink.symlink_to_file(file.path()).unwrap();
2270
2271 let permissions = tokio::fs::symlink_metadata(symlink.path())
2273 .await
2274 .unwrap()
2275 .permissions();
2276 assert!(
2277 !permissions.readonly(),
2278 "Symlink is already set to readonly"
2279 );
2280
2281 api.set_permissions(
2283 ctx,
2284 symlink.path().to_path_buf(),
2285 Permissions::readonly(),
2286 SetPermissionsOptions {
2287 exclude_symlinks: true,
2288 ..Default::default()
2289 },
2290 )
2291 .await
2292 .unwrap();
2293
2294 let permissions = tokio::fs::symlink_metadata(symlink.path())
2296 .await
2297 .unwrap()
2298 .permissions();
2299 assert!(
2300 !permissions.readonly(),
2301 "Symlink (or file underneath) set to readonly"
2302 );
2303 }
2304
2305 #[test(tokio::test)]
2306 async fn set_permissions_should_support_recursive_if_option_specified() {
2307 let (api, ctx, _rx) = setup().await;
2308 let temp = assert_fs::TempDir::new().unwrap();
2309 let file = temp.child("file");
2310 file.write_str("some text").unwrap();
2311
2312 let permissions = tokio::fs::symlink_metadata(temp.path())
2314 .await
2315 .unwrap()
2316 .permissions();
2317 assert!(
2318 !permissions.readonly(),
2319 "Temp dir is already set to readonly"
2320 );
2321
2322 let permissions = tokio::fs::symlink_metadata(file.path())
2324 .await
2325 .unwrap()
2326 .permissions();
2327 assert!(!permissions.readonly(), "File is already set to readonly");
2328
2329 api.set_permissions(
2331 ctx,
2332 temp.path().to_path_buf(),
2333 Permissions::readonly(),
2334 SetPermissionsOptions {
2335 recursive: true,
2336 ..Default::default()
2337 },
2338 )
2339 .await
2340 .unwrap();
2341
2342 let permissions = tokio::fs::symlink_metadata(temp.path())
2344 .await
2345 .unwrap()
2346 .permissions();
2347 assert!(permissions.readonly(), "Temp directory not set to readonly");
2348
2349 let permissions = tokio::fs::symlink_metadata(file.path())
2350 .await
2351 .unwrap()
2352 .permissions();
2353 assert!(permissions.readonly(), "File not set to readonly");
2354 }
2355
2356 #[test(tokio::test)]
2357 async fn set_permissions_should_support_following_explicit_symlink_if_option_specified() {
2358 let (api, ctx, _rx) = setup().await;
2359 let temp = assert_fs::TempDir::new().unwrap();
2360 let file = temp.child("file");
2361 file.write_str("some text").unwrap();
2362
2363 let symlink = temp.child("link");
2364 symlink.symlink_to_file(file.path()).unwrap();
2365
2366 let permissions = tokio::fs::symlink_metadata(file.path())
2368 .await
2369 .unwrap()
2370 .permissions();
2371 assert!(!permissions.readonly(), "File is already set to readonly");
2372
2373 let permissions = tokio::fs::symlink_metadata(symlink.path())
2375 .await
2376 .unwrap()
2377 .permissions();
2378 assert!(
2379 !permissions.readonly(),
2380 "Symlink is already set to readonly"
2381 );
2382
2383 api.set_permissions(
2385 ctx,
2386 symlink.path().to_path_buf(),
2387 Permissions::readonly(),
2388 SetPermissionsOptions {
2389 follow_symlinks: true,
2390 ..Default::default()
2391 },
2392 )
2393 .await
2394 .unwrap();
2395
2396 let permissions = tokio::fs::symlink_metadata(file.path())
2398 .await
2399 .unwrap()
2400 .permissions();
2401 assert!(permissions.readonly(), "File not set to readonly");
2402
2403 let permissions = tokio::fs::symlink_metadata(symlink.path())
2404 .await
2405 .unwrap()
2406 .permissions();
2407 assert!(
2408 !permissions.readonly(),
2409 "Symlink unexpectedly set to readonly"
2410 );
2411 }
2412
2413 #[test(tokio::test)]
2416 #[cfg_attr(windows, ignore)]
2417 async fn proc_spawn_should_send_error_on_failure() {
2418 let (api, ctx, _rx) = setup().await;
2419
2420 let _ = api
2421 .proc_spawn(
2422 ctx,
2423 DOES_NOT_EXIST_BIN.to_str().unwrap().to_string(),
2424 Environment::new(),
2425 None,
2426 None,
2427 )
2428 .await
2429 .unwrap_err();
2430 }
2431
2432 #[test(tokio::test)]
2435 #[cfg_attr(windows, ignore)]
2436 async fn proc_spawn_should_return_id_of_spawned_process() {
2437 let (api, ctx, _rx) = setup().await;
2438
2439 let id = api
2440 .proc_spawn(
2441 ctx,
2442 format!(
2444 "{} {}",
2445 *SCRIPT_RUNNER,
2446 ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap()
2447 ),
2448 Environment::new(),
2449 None,
2450 None,
2451 )
2452 .await
2453 .unwrap();
2454 assert!(id > 0);
2455 }
2456
2457 #[test(tokio::test)]
2460 #[cfg_attr(windows, ignore)]
2461 async fn proc_spawn_should_send_back_stdout_periodically_when_available() {
2462 let (api, ctx, mut rx) = setup().await;
2463
2464 let proc_id = api
2465 .proc_spawn(
2466 ctx,
2467 format!(
2469 "{} {} some stdout",
2470 *SCRIPT_RUNNER,
2471 ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap()
2472 ),
2473 Environment::new(),
2474 None,
2475 None,
2476 )
2477 .await
2478 .unwrap();
2479
2480 let data_1 = rx.recv().await.expect("Missing first response");
2488 let data_2 = rx.recv().await.expect("Missing second response");
2489
2490 let mut got_stdout = false;
2491 let mut got_done = false;
2492
2493 let mut check_data = |data: &Response| match data {
2494 Response::ProcStdout { id, data } => {
2495 assert_eq!(
2496 *id, proc_id,
2497 "Got {}, but expected {} as process id",
2498 id, proc_id
2499 );
2500 assert_eq!(data, b"some stdout", "Got wrong stdout");
2501 got_stdout = true;
2502 }
2503 Response::ProcDone { id, success, .. } => {
2504 assert_eq!(
2505 *id, proc_id,
2506 "Got {}, but expected {} as process id",
2507 id, proc_id
2508 );
2509 assert!(success, "Process should have completed successfully");
2510 got_done = true;
2511 }
2512 x => panic!("Unexpected response: {:?}", x),
2513 };
2514
2515 check_data(&data_1);
2516 check_data(&data_2);
2517 assert!(got_stdout, "Missing stdout response");
2518 assert!(got_done, "Missing done response");
2519 }
2520
2521 #[test(tokio::test)]
2524 #[cfg_attr(windows, ignore)]
2525 async fn proc_spawn_should_send_back_stderr_periodically_when_available() {
2526 let (api, ctx, mut rx) = setup().await;
2527
2528 let proc_id = api
2529 .proc_spawn(
2530 ctx,
2531 format!(
2533 "{} {} some stderr",
2534 *SCRIPT_RUNNER,
2535 ECHO_ARGS_TO_STDERR_SH.to_str().unwrap()
2536 ),
2537 Environment::new(),
2538 None,
2539 None,
2540 )
2541 .await
2542 .unwrap();
2543
2544 let data_1 = rx.recv().await.expect("Missing first response");
2552 let data_2 = rx.recv().await.expect("Missing second response");
2553
2554 let mut got_stderr = false;
2555 let mut got_done = false;
2556
2557 let mut check_data = |data: &Response| match data {
2558 Response::ProcStderr { id, data } => {
2559 assert_eq!(
2560 *id, proc_id,
2561 "Got {}, but expected {} as process id",
2562 id, proc_id
2563 );
2564 assert_eq!(data, b"some stderr", "Got wrong stderr");
2565 got_stderr = true;
2566 }
2567 Response::ProcDone { id, success, .. } => {
2568 assert_eq!(
2569 *id, proc_id,
2570 "Got {}, but expected {} as process id",
2571 id, proc_id
2572 );
2573 assert!(success, "Process should have completed successfully");
2574 got_done = true;
2575 }
2576 x => panic!("Unexpected response: {:?}", x),
2577 };
2578
2579 check_data(&data_1);
2580 check_data(&data_2);
2581 assert!(got_stderr, "Missing stderr response");
2582 assert!(got_done, "Missing done response");
2583 }
2584
2585 #[test(tokio::test)]
2588 #[cfg_attr(windows, ignore)]
2589 async fn proc_spawn_should_send_done_signal_when_completed() {
2590 let (api, ctx, mut rx) = setup().await;
2591
2592 let proc_id = api
2593 .proc_spawn(
2594 ctx,
2595 format!("{} {} 0.1", *SCRIPT_RUNNER, SLEEP_SH.to_str().unwrap()),
2597 Environment::new(),
2598 None,
2599 None,
2600 )
2601 .await
2602 .unwrap();
2603
2604 match rx.recv().await.unwrap() {
2606 Response::ProcDone { id, .. } => assert_eq!(
2607 id, proc_id,
2608 "Got {}, but expected {} as process id",
2609 id, proc_id
2610 ),
2611 x => panic!("Unexpected response: {:?}", x),
2612 }
2613 }
2614
2615 #[test(tokio::test)]
2618 #[cfg_attr(windows, ignore)]
2619 async fn proc_spawn_should_clear_process_from_state_when_killed() {
2620 let (api, ctx_1, mut rx) = setup().await;
2621 let (ctx_2, _rx) = {
2622 let (reply, rx) = make_reply();
2623 let ctx = DistantCtx {
2624 connection_id: ctx_1.connection_id,
2625 reply,
2626 };
2627 (ctx, rx)
2628 };
2629
2630 let proc_id = api
2631 .proc_spawn(
2632 ctx_1,
2633 format!("{} {} 1", *SCRIPT_RUNNER, SLEEP_SH.to_str().unwrap()),
2635 Environment::new(),
2636 None,
2637 None,
2638 )
2639 .await
2640 .unwrap();
2641
2642 api.proc_kill(ctx_2, proc_id).await.unwrap();
2644
2645 match rx.recv().await.unwrap() {
2647 Response::ProcDone { id, .. } => assert_eq!(
2648 id, proc_id,
2649 "Got {}, but expected {} as process id",
2650 id, proc_id
2651 ),
2652 x => panic!("Unexpected response: {:?}", x),
2653 }
2654 }
2655
2656 #[test(tokio::test)]
2657 async fn proc_kill_should_fail_if_given_non_existent_process() {
2658 let (api, ctx, _rx) = setup().await;
2659
2660 let _ = api.proc_kill(ctx, 0xDEADBEEF).await.unwrap_err();
2662 }
2663
2664 #[test(tokio::test)]
2665 async fn proc_stdin_should_fail_if_given_non_existent_process() {
2666 let (api, ctx, _rx) = setup().await;
2667
2668 let _ = api
2670 .proc_stdin(ctx, 0xDEADBEEF, b"some input".to_vec())
2671 .await
2672 .unwrap_err();
2673 }
2674
2675 #[test(tokio::test)]
2678 #[cfg_attr(windows, ignore)]
2679 async fn proc_stdin_should_send_stdin_to_process() {
2680 let (api, ctx_1, mut rx) = setup().await;
2681 let (ctx_2, _rx) = {
2682 let (reply, rx) = make_reply();
2683 let ctx = DistantCtx {
2684 connection_id: ctx_1.connection_id,
2685 reply,
2686 };
2687 (ctx, rx)
2688 };
2689
2690 let id = api
2692 .proc_spawn(
2693 ctx_1,
2694 format!(
2696 "{} {}",
2697 *SCRIPT_RUNNER,
2698 ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap()
2699 ),
2700 Environment::new(),
2701 None,
2702 None,
2703 )
2704 .await
2705 .unwrap();
2706
2707 api.proc_stdin(ctx_2, id, b"hello world\n".to_vec())
2709 .await
2710 .unwrap();
2711
2712 match rx.recv().await.unwrap() {
2714 Response::ProcStdout { data, .. } => {
2715 assert_eq!(data, b"hello world\n", "Mirrored data didn't match");
2716 }
2717 x => panic!("Unexpected response: {:?}", x),
2718 }
2719 }
2720
2721 #[test(tokio::test)]
2722 async fn system_info_should_return_system_info_based_on_binary() {
2723 let (api, ctx, _rx) = setup().await;
2724
2725 let system_info = api.system_info(ctx).await.unwrap();
2726 assert_eq!(
2727 system_info,
2728 SystemInfo {
2729 family: std::env::consts::FAMILY.to_string(),
2730 os: std::env::consts::OS.to_string(),
2731 arch: std::env::consts::ARCH.to_string(),
2732 current_dir: std::env::current_dir().unwrap_or_default(),
2733 main_separator: std::path::MAIN_SEPARATOR,
2734 username: whoami::username(),
2735 shell: if cfg!(windows) {
2736 std::env::var("ComSpec").unwrap_or_else(|_| String::from("cmd.exe"))
2737 } else {
2738 std::env::var("SHELL").unwrap_or_else(|_| String::from("/bin/sh"))
2739 }
2740 }
2741 );
2742 }
2743}