mirror of https://github.com/chipsenkbeil/distant
Add filesystem watching & remove distant-lua (#102)
parent
f46eeea8d5
commit
268ec948d6
@ -0,0 +1,544 @@
|
||||
use crate::{
|
||||
client::{SessionChannel, SessionChannelExt, SessionChannelExtError},
|
||||
constants::CLIENT_WATCHER_CAPACITY,
|
||||
data::{Change, ChangeKindSet, Error as DistantError, Request, RequestData, ResponseData},
|
||||
net::TransportError,
|
||||
};
|
||||
use derive_more::{Display, Error, From};
|
||||
use log::*;
|
||||
use std::{
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tokio::{sync::mpsc, task::JoinHandle};
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum WatchError {
|
||||
/// When no confirmation of watch is received
|
||||
MissingConfirmation,
|
||||
|
||||
/// A server-side error occurred when attempting to watch
|
||||
ServerError(DistantError),
|
||||
|
||||
/// When the communication over the wire has issues
|
||||
TransportError(TransportError),
|
||||
|
||||
/// When a queued change is dropped because the response channel closed early
|
||||
QueuedChangeDropped,
|
||||
|
||||
/// Some unexpected response was received when attempting to watch
|
||||
#[display(fmt = "Unexpected response: {:?}", _0)]
|
||||
UnexpectedResponse(#[error(not(source))] ResponseData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, From, Error)]
|
||||
pub struct UnwatchError(SessionChannelExtError);
|
||||
|
||||
/// Represents a watcher of some path on a remote machine
|
||||
pub struct Watcher {
|
||||
tenant: String,
|
||||
channel: SessionChannel,
|
||||
path: PathBuf,
|
||||
task: JoinHandle<()>,
|
||||
rx: mpsc::Receiver<Change>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Watcher {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Watcher")
|
||||
.field("tenant", &self.tenant)
|
||||
.field("path", &self.path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
/// Creates a watcher for some remote path
|
||||
pub async fn watch(
|
||||
tenant: impl Into<String>,
|
||||
mut channel: SessionChannel,
|
||||
path: impl Into<PathBuf>,
|
||||
recursive: bool,
|
||||
only: impl Into<ChangeKindSet>,
|
||||
except: impl Into<ChangeKindSet>,
|
||||
) -> Result<Self, WatchError> {
|
||||
let tenant = tenant.into();
|
||||
let path = path.into();
|
||||
let only = only.into();
|
||||
let except = except.into();
|
||||
trace!(
|
||||
"Watching {:?} (recursive = {}){}{}",
|
||||
path,
|
||||
recursive,
|
||||
if only.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" (only = {})", only)
|
||||
},
|
||||
if except.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" (except = {})", except)
|
||||
},
|
||||
);
|
||||
|
||||
// Submit our run request and get back a mailbox for responses
|
||||
let mut mailbox = channel
|
||||
.mail(Request::new(
|
||||
tenant.as_str(),
|
||||
vec![RequestData::Watch {
|
||||
path: path.to_path_buf(),
|
||||
recursive,
|
||||
only: only.into_vec(),
|
||||
except: except.into_vec(),
|
||||
}],
|
||||
))
|
||||
.await
|
||||
.map_err(WatchError::TransportError)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel(CLIENT_WATCHER_CAPACITY);
|
||||
|
||||
// Wait to get the confirmation of watch as either ok or error
|
||||
let mut queue: Vec<Change> = Vec::new();
|
||||
let mut confirmed = false;
|
||||
while let Some(res) = mailbox.next().await {
|
||||
for data in res.payload {
|
||||
match data {
|
||||
ResponseData::Changed(change) => queue.push(change),
|
||||
ResponseData::Ok => {
|
||||
confirmed = true;
|
||||
}
|
||||
ResponseData::Error(x) => return Err(WatchError::ServerError(x)),
|
||||
x => return Err(WatchError::UnexpectedResponse(x)),
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if we got the confirmation
|
||||
// NOTE: Doing this later because we want to make sure the entire payload is processed
|
||||
// first before exiting the loop
|
||||
if confirmed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send out any of our queued changes that we got prior to the acknowledgement
|
||||
trace!("Forwarding {} queued changes for {:?}", queue.len(), path);
|
||||
for change in queue {
|
||||
if tx.send(change).await.is_err() {
|
||||
return Err(WatchError::QueuedChangeDropped);
|
||||
}
|
||||
}
|
||||
|
||||
// If we never received an acknowledgement of watch before the mailbox closed,
|
||||
// fail with a missing confirmation error
|
||||
if !confirmed {
|
||||
return Err(WatchError::MissingConfirmation);
|
||||
}
|
||||
|
||||
// Spawn a task that continues to look for change events, discarding anything
|
||||
// else that it gets
|
||||
let task = tokio::spawn({
|
||||
let path = path.clone();
|
||||
async move {
|
||||
while let Some(res) = mailbox.next().await {
|
||||
for data in res.payload {
|
||||
match data {
|
||||
ResponseData::Changed(change) => {
|
||||
// If we can't queue up a change anymore, we've
|
||||
// been closed and therefore want to quit
|
||||
if tx.is_closed() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, send over the change
|
||||
if let Err(x) = tx.send(change).await {
|
||||
error!(
|
||||
"Watcher for {:?} failed to send change {:?}",
|
||||
path, x.0
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
tenant,
|
||||
path,
|
||||
channel,
|
||||
task,
|
||||
rx,
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the path this watcher is monitoring
|
||||
pub fn path(&self) -> &Path {
|
||||
self.path.as_path()
|
||||
}
|
||||
|
||||
/// Returns true if the watcher is still actively watching for changes
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
|
||||
/// Returns the next change detected by the watcher, or none if the watcher has concluded
|
||||
pub async fn next(&mut self) -> Option<Change> {
|
||||
self.rx.recv().await
|
||||
}
|
||||
|
||||
/// Unwatches the path being watched, closing out the watcher
|
||||
pub async fn unwatch(&mut self) -> Result<(), UnwatchError> {
|
||||
trace!("Unwatching {:?}", self.path);
|
||||
let result = self
|
||||
.channel
|
||||
.unwatch(self.tenant.to_string(), self.path.to_path_buf())
|
||||
.await
|
||||
.map_err(UnwatchError::from);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// Kill our task that processes inbound changes if we
|
||||
// have successfully unwatched the path
|
||||
self.task.abort();
|
||||
self.active = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(x) => Err(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
client::Session,
|
||||
data::{ChangeKind, Response},
|
||||
net::{InmemoryStream, PlainCodec, Transport},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
fn make_session() -> (Transport<InmemoryStream, PlainCodec>, Session) {
|
||||
let (t1, t2) = Transport::make_pair();
|
||||
(t1, Session::initialize(t2).unwrap())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watcher_should_have_path_reflect_watched_path() {
|
||||
let (mut transport, session) = make_session();
|
||||
let test_path = Path::new("/some/test/path");
|
||||
|
||||
// Create a task for watcher as we need to handle the request and a response
|
||||
// in a separate async block
|
||||
let watch_task = tokio::spawn(async move {
|
||||
Watcher::watch(
|
||||
String::from("test-tenant"),
|
||||
session.clone_channel(),
|
||||
test_path,
|
||||
true,
|
||||
ChangeKindSet::empty(),
|
||||
ChangeKindSet::empty(),
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
// Wait until we get the request from the session
|
||||
let req = transport.receive::<Request>().await.unwrap().unwrap();
|
||||
|
||||
// Send back an acknowledgement that a watcher was created
|
||||
transport
|
||||
.send(Response::new("test-tenant", req.id, vec![ResponseData::Ok]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get the watcher and verify the path
|
||||
let watcher = watch_task.await.unwrap().unwrap();
|
||||
assert_eq!(watcher.path(), test_path);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watcher_should_support_getting_next_change() {
|
||||
let (mut transport, session) = make_session();
|
||||
let test_path = Path::new("/some/test/path");
|
||||
|
||||
// Create a task for watcher as we need to handle the request and a response
|
||||
// in a separate async block
|
||||
let watch_task = tokio::spawn(async move {
|
||||
Watcher::watch(
|
||||
String::from("test-tenant"),
|
||||
session.clone_channel(),
|
||||
test_path,
|
||||
true,
|
||||
ChangeKindSet::empty(),
|
||||
ChangeKindSet::empty(),
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
// Wait until we get the request from the session
|
||||
let req = transport.receive::<Request>().await.unwrap().unwrap();
|
||||
|
||||
// Send back an acknowledgement that a watcher was created
|
||||
transport
|
||||
.send(Response::new("test-tenant", req.id, vec![ResponseData::Ok]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get the watcher
|
||||
let mut watcher = watch_task.await.unwrap().unwrap();
|
||||
|
||||
// Send some changes related to the file
|
||||
transport
|
||||
.send(Response::new(
|
||||
"test-tenant",
|
||||
req.id,
|
||||
vec![
|
||||
ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Access,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
}),
|
||||
ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Content,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
}),
|
||||
],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify that the watcher gets the changes, one at a time
|
||||
let change = watcher.next().await.expect("Watcher closed unexpectedly");
|
||||
assert_eq!(
|
||||
change,
|
||||
Change {
|
||||
kind: ChangeKind::Access,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
}
|
||||
);
|
||||
|
||||
let change = watcher.next().await.expect("Watcher closed unexpectedly");
|
||||
assert_eq!(
|
||||
change,
|
||||
Change {
|
||||
kind: ChangeKind::Content,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watcher_should_distinguish_change_events_and_only_receive_changes_for_itself() {
|
||||
let (mut transport, session) = make_session();
|
||||
let test_path = Path::new("/some/test/path");
|
||||
|
||||
// Create a task for watcher as we need to handle the request and a response
|
||||
// in a separate async block
|
||||
let watch_task = tokio::spawn(async move {
|
||||
Watcher::watch(
|
||||
String::from("test-tenant"),
|
||||
session.clone_channel(),
|
||||
test_path,
|
||||
true,
|
||||
ChangeKindSet::empty(),
|
||||
ChangeKindSet::empty(),
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
// Wait until we get the request from the session
|
||||
let req = transport.receive::<Request>().await.unwrap().unwrap();
|
||||
|
||||
// Send back an acknowledgement that a watcher was created
|
||||
transport
|
||||
.send(Response::new("test-tenant", req.id, vec![ResponseData::Ok]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get the watcher
|
||||
let mut watcher = watch_task.await.unwrap().unwrap();
|
||||
|
||||
// Send a change from the appropriate origin
|
||||
transport
|
||||
.send(Response::new(
|
||||
"test-tenant",
|
||||
req.id,
|
||||
vec![ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Access,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
})],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send a change from a different origin
|
||||
transport
|
||||
.send(Response::new(
|
||||
"test-tenant",
|
||||
req.id + 1,
|
||||
vec![ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Content,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
})],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send a change from the appropriate origin
|
||||
transport
|
||||
.send(Response::new(
|
||||
"test-tenant",
|
||||
req.id,
|
||||
vec![ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Remove,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
})],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify that the watcher gets the changes, one at a time
|
||||
let change = watcher.next().await.expect("Watcher closed unexpectedly");
|
||||
assert_eq!(
|
||||
change,
|
||||
Change {
|
||||
kind: ChangeKind::Access,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
}
|
||||
);
|
||||
|
||||
let change = watcher.next().await.expect("Watcher closed unexpectedly");
|
||||
assert_eq!(
|
||||
change,
|
||||
Change {
|
||||
kind: ChangeKind::Remove,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watcher_should_stop_receiving_events_if_unwatched() {
|
||||
let (mut transport, session) = make_session();
|
||||
let test_path = Path::new("/some/test/path");
|
||||
|
||||
// Create a task for watcher as we need to handle the request and a response
|
||||
// in a separate async block
|
||||
let watch_task = tokio::spawn(async move {
|
||||
Watcher::watch(
|
||||
String::from("test-tenant"),
|
||||
session.clone_channel(),
|
||||
test_path,
|
||||
true,
|
||||
ChangeKindSet::empty(),
|
||||
ChangeKindSet::empty(),
|
||||
)
|
||||
.await
|
||||
});
|
||||
|
||||
// Wait until we get the request from the session
|
||||
let req = transport.receive::<Request>().await.unwrap().unwrap();
|
||||
|
||||
// Send back an acknowledgement that a watcher was created
|
||||
transport
|
||||
.send(Response::new("test-tenant", req.id, vec![ResponseData::Ok]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send some changes from the appropriate origin
|
||||
transport
|
||||
.send(Response::new(
|
||||
"test-tenant",
|
||||
req.id,
|
||||
vec![
|
||||
ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Access,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
}),
|
||||
ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Content,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
}),
|
||||
ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Remove,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
}),
|
||||
],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Wait a little bit for all changes to be queued
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
|
||||
// Create a task for for unwatching as we need to handle the request and a response
|
||||
// in a separate async block
|
||||
let watcher = Arc::new(Mutex::new(watch_task.await.unwrap().unwrap()));
|
||||
|
||||
// Verify that the watcher gets the first change
|
||||
let change = watcher
|
||||
.lock()
|
||||
.await
|
||||
.next()
|
||||
.await
|
||||
.expect("Watcher closed unexpectedly");
|
||||
assert_eq!(
|
||||
change,
|
||||
Change {
|
||||
kind: ChangeKind::Access,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
}
|
||||
);
|
||||
|
||||
// Unwatch the watcher, verify the request is sent out, and respond with ok
|
||||
let watcher_2 = Arc::clone(&watcher);
|
||||
let unwatch_task = tokio::spawn(async move { watcher_2.lock().await.unwatch().await });
|
||||
|
||||
let req = transport.receive::<Request>().await.unwrap().unwrap();
|
||||
|
||||
transport
|
||||
.send(Response::new("test-tenant", req.id, vec![ResponseData::Ok]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Wait for the unwatch to complete
|
||||
let _ = unwatch_task.await.unwrap().unwrap();
|
||||
|
||||
transport
|
||||
.send(Response::new(
|
||||
"test-tenant",
|
||||
req.id,
|
||||
vec![ResponseData::Changed(Change {
|
||||
kind: ChangeKind::Unknown,
|
||||
paths: vec![test_path.to_path_buf()],
|
||||
})],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify that we get any remaining changes that were received before unwatched,
|
||||
// but nothing new after that
|
||||
assert_eq!(
|
||||
watcher.lock().await.next().await,
|
||||
Some(Change {
|
||||
kind: ChangeKind::Content,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
watcher.lock().await.next().await,
|
||||
Some(Change {
|
||||
kind: ChangeKind::Remove,
|
||||
paths: vec![test_path.to_path_buf()]
|
||||
})
|
||||
);
|
||||
assert_eq!(watcher.lock().await.next().await, None);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "link-args=-rdynamic"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = ["-C", "link-args=-rdynamic"]
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-args=-rdynamic"]
|
@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "distant-lua-tests"
|
||||
description = "Tests for distant-lua crate"
|
||||
version = "0.0.0"
|
||||
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["lua51", "vendored"]
|
||||
lua54 = ["mlua/lua54"]
|
||||
lua53 = ["mlua/lua53"]
|
||||
lua52 = ["mlua/lua52"]
|
||||
lua51 = ["mlua/lua51"]
|
||||
luajit = ["mlua/luajit"]
|
||||
vendored = ["mlua/vendored"]
|
||||
|
||||
[dependencies]
|
||||
assert_fs = "1.0.4"
|
||||
distant-core = { path = "../distant-core" }
|
||||
futures = "0.3.17"
|
||||
indoc = "1.0.3"
|
||||
mlua = { version = "0.7.3", features = ["async", "macros", "serialize"] }
|
||||
once_cell = "1.8.0"
|
||||
predicates = "2.0.2"
|
||||
rstest = "0.11.0"
|
||||
tokio = { version = "1.12.0", features = ["rt", "sync"] }
|
@ -1,34 +0,0 @@
|
||||
# Tests for Distant Lua (module)
|
||||
|
||||
Contains tests for the **distant-lua** module. These tests must be in a
|
||||
separate crate due to linking restrictions as described in
|
||||
[khvzak/mlua#79](https://github.com/khvzak/mlua/issues/79).
|
||||
|
||||
## Tests
|
||||
|
||||
You must run these tests from within this directory, not from the root of the
|
||||
repository. Additionally, you must build the Lua module **before** running
|
||||
these tests!
|
||||
|
||||
```bash
|
||||
# From root of repository
|
||||
(cd distant-lua && cargo build --release)
|
||||
```
|
||||
|
||||
Running the tests themselves:
|
||||
|
||||
```bash
|
||||
# From root of repository
|
||||
(cd distant-lua-tests && cargo test --release)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under either of
|
||||
|
||||
Apache License, Version 2.0, (LICENSE-APACHE or
|
||||
[apache-license][apache-license]) MIT license (LICENSE-MIT or
|
||||
[mit-license][mit-license]) at your option.
|
||||
|
||||
[apache-license]: http://www.apache.org/licenses/LICENSE-2.0
|
||||
[mit-license]: http://opensource.org/licenses/MIT
|
@ -1,74 +0,0 @@
|
||||
use distant_core::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use rstest::*;
|
||||
use std::{net::SocketAddr, thread};
|
||||
use tokio::{runtime::Runtime, sync::mpsc};
|
||||
|
||||
/// Context for some listening distant server
|
||||
pub struct DistantServerCtx {
|
||||
pub addr: SocketAddr,
|
||||
pub key: String,
|
||||
done_tx: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
impl DistantServerCtx {
|
||||
pub fn initialize() -> Self {
|
||||
let ip_addr = "127.0.0.1".parse().unwrap();
|
||||
let (done_tx, mut done_rx) = mpsc::channel(1);
|
||||
let (started_tx, mut started_rx) = mpsc::channel(1);
|
||||
|
||||
// NOTE: We spawn a dedicated thread that runs our tokio runtime separately from our test
|
||||
// itself because using lua blocks the thread and prevents our runtime from working unless
|
||||
// we make the tokio test multi-threaded using `tokio::test(flavor = "multi_thread",
|
||||
// worker_threads = 1)` which isn't great because we're only using async tests for our
|
||||
// server itself; so, we hide that away since our test logic doesn't need to be async
|
||||
thread::spawn(move || match Runtime::new() {
|
||||
Ok(rt) => {
|
||||
rt.block_on(async move {
|
||||
let opts = DistantServerOptions {
|
||||
shutdown_after: None,
|
||||
max_msg_capacity: 100,
|
||||
};
|
||||
let key = SecretKey::default();
|
||||
let key_hex_string = key.unprotected_to_hex_key();
|
||||
let codec = XChaCha20Poly1305Codec::from(key);
|
||||
let (_server, port) =
|
||||
DistantServer::bind(ip_addr, "0".parse().unwrap(), codec, opts)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
started_tx.send(Ok((port, key_hex_string))).await.unwrap();
|
||||
|
||||
let _ = done_rx.recv().await;
|
||||
});
|
||||
}
|
||||
Err(x) => {
|
||||
started_tx.blocking_send(Err(x)).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Extract our server startup data if we succeeded
|
||||
let (port, key) = started_rx.blocking_recv().unwrap().unwrap();
|
||||
|
||||
Self {
|
||||
addr: SocketAddr::new(ip_addr, port),
|
||||
key,
|
||||
done_tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DistantServerCtx {
|
||||
/// Kills server upon drop
|
||||
fn drop(&mut self) {
|
||||
let _ = self.done_tx.send(());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the global distant server
|
||||
#[fixture]
|
||||
pub fn ctx() -> &'static DistantServerCtx {
|
||||
static CTX: OnceCell<DistantServerCtx> = OnceCell::new();
|
||||
|
||||
CTX.get_or_init(DistantServerCtx::initialize)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
use mlua::prelude::*;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
pub fn make() -> LuaResult<Lua> {
|
||||
let (dylib_path, dylib_ext, separator);
|
||||
if cfg!(target_os = "macos") {
|
||||
dylib_path = env::var("DYLD_FALLBACK_LIBRARY_PATH").unwrap();
|
||||
dylib_ext = "dylib";
|
||||
separator = ":";
|
||||
} else if cfg!(target_os = "linux") {
|
||||
dylib_path = env::var("LD_LIBRARY_PATH").unwrap();
|
||||
dylib_ext = "so";
|
||||
separator = ":";
|
||||
} else if cfg!(target_os = "windows") {
|
||||
dylib_path = env::var("PATH").unwrap();
|
||||
dylib_ext = "dll";
|
||||
separator = ";";
|
||||
} else {
|
||||
panic!("unknown target os");
|
||||
};
|
||||
|
||||
let mut cpath = dylib_path
|
||||
.split(separator)
|
||||
.take(3)
|
||||
.map(|p| {
|
||||
let mut path = PathBuf::from(p);
|
||||
path.push(format!("lib?.{}", dylib_ext));
|
||||
path.to_str().unwrap().to_owned()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(";");
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
cpath = cpath.replace("\\", "\\\\");
|
||||
cpath = cpath.replace("lib?.", "?.");
|
||||
}
|
||||
|
||||
let lua = unsafe { Lua::unsafe_new() }; // To be able to load C modules
|
||||
lua.load(&format!("package.cpath = \"{}\"", cpath)).exec()?;
|
||||
Ok(lua)
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub mod fixtures;
|
||||
pub mod lua;
|
||||
pub mod poll;
|
||||
pub mod session;
|
@ -1,17 +0,0 @@
|
||||
use mlua::{chunk, prelude::*};
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
/// Creates a function that can be passed as the schedule function for `wrap_async`
|
||||
pub fn make_function(lua: &Lua) -> LuaResult<LuaFunction> {
|
||||
let sleep = lua.create_function(|_, ()| {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
lua.load(chunk! {
|
||||
local cb = ...
|
||||
$sleep()
|
||||
cb()
|
||||
})
|
||||
.into_function()
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
use super::fixtures::DistantServerCtx;
|
||||
use mlua::{chunk, prelude::*};
|
||||
|
||||
/// Creates a function that produces a session within the provided Lua environment
|
||||
/// using the given distant server context, returning the session's id
|
||||
pub fn make_function<'a>(lua: &'a Lua, ctx: &'_ DistantServerCtx) -> LuaResult<LuaFunction<'a>> {
|
||||
let addr = ctx.addr;
|
||||
let host = addr.ip().to_string();
|
||||
let port = addr.port();
|
||||
let key = ctx.key.clone();
|
||||
|
||||
lua.load(chunk! {
|
||||
local distant = require("distant_lua")
|
||||
local thread = coroutine.create(distant.session.connect_async)
|
||||
|
||||
local status, res = coroutine.resume(thread, {
|
||||
host = $host,
|
||||
port = $port,
|
||||
key = $key,
|
||||
timeout = 15000,
|
||||
})
|
||||
|
||||
// Block until the connection finishes
|
||||
local session = nil
|
||||
while status do
|
||||
if status and res ~= distant.pending then
|
||||
session = res
|
||||
break
|
||||
end
|
||||
|
||||
status, res = coroutine.resume(thread)
|
||||
end
|
||||
|
||||
if session then
|
||||
return session
|
||||
else
|
||||
error(res)
|
||||
end
|
||||
})
|
||||
.into_function()
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
mod common;
|
||||
mod lua;
|
@ -1,79 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.append_file_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, data = $data }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_append_data_to_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.append_file_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, data = $data }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("line 1some text");
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.append_file_text_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, text = $text }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_append_text_to_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.append_file_text_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, text = $text }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("line 1some text");
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_send_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.copy_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that destination does not exist
|
||||
dst.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_an_entire_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let src_file = src.child("file");
|
||||
src_file.write_str("some contents").unwrap();
|
||||
|
||||
let dst = temp.child("dst");
|
||||
let dst_file = dst.child("file");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.copy_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we have source and destination directories and associated contents
|
||||
src.assert(predicate::path::is_dir());
|
||||
src_file.assert(predicate::path::is_file());
|
||||
dst.assert(predicate::path::is_dir());
|
||||
dst_file.assert(predicate::path::eq_file(src_file.path()));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_an_empty_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.copy_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we still have source and destination directories
|
||||
src.assert(predicate::path::is_dir());
|
||||
dst.assert(predicate::path::is_dir());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_a_directory_that_only_contains_directories(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let src_dir = src.child("dir");
|
||||
src_dir.create_dir_all().unwrap();
|
||||
|
||||
let dst = temp.child("dst");
|
||||
let dst_dir = dst.child("dir");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.copy_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we have source and destination directories and associated contents
|
||||
src.assert(predicate::path::is_dir().name("src"));
|
||||
src_dir.assert(predicate::path::is_dir().name("src/dir"));
|
||||
dst.assert(predicate::path::is_dir().name("dst"));
|
||||
dst_dir.assert(predicate::path::is_dir().name("dst/dir"));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_a_single_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.write_str("some text").unwrap();
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.copy_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we still have source and that destination has source's contents
|
||||
src.assert(predicate::path::is_file());
|
||||
dst.assert(predicate::path::eq_file(src.path()));
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
// /root/
|
||||
// /root/file1
|
||||
// /root/link1 -> /root/sub1/file2
|
||||
// /root/sub1/
|
||||
// /root/sub1/file2
|
||||
fn setup_dir() -> assert_fs::TempDir {
|
||||
let root_dir = assert_fs::TempDir::new().unwrap();
|
||||
root_dir.child("file1").touch().unwrap();
|
||||
|
||||
let sub1 = root_dir.child("sub1");
|
||||
sub1.create_dir_all().unwrap();
|
||||
|
||||
let file2 = sub1.child("file2");
|
||||
file2.touch().unwrap();
|
||||
|
||||
let link1 = root_dir.child("link1");
|
||||
link1.symlink_to_file(file2.path()).unwrap();
|
||||
|
||||
root_dir
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_send_error_if_fails(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Make a path that has multiple non-existent components
|
||||
// so the creation will fail
|
||||
let root_dir = setup_dir();
|
||||
let path = root_dir.path().join("nested").join("new-dir");
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.create_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $path_str }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that the directory was not actually created
|
||||
assert!(!path.exists(), "Path unexpectedly exists");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_send_ok_when_successful(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let root_dir = setup_dir();
|
||||
let path = root_dir.path().join("new-dir");
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.create_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $path_str }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that the directory was actually created
|
||||
assert!(path.exists(), "Directory not created");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_creating_multiple_dir_components(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let root_dir = setup_dir();
|
||||
let path = root_dir.path().join("nested").join("new-dir");
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.create_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $path_str, all = true }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that the directory was actually created
|
||||
assert!(path.exists(), "Directory not created");
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_send_true_if_path_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.touch().unwrap();
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.exists_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, exists
|
||||
f(session, { path = $file_path }, function(success, res)
|
||||
if success then
|
||||
exists = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(exists == true, "Invalid exists return value")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_send_false_if_path_does_not_exist(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.exists_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, exists
|
||||
f(session, { path = $file_path }, function(success, res)
|
||||
if success then
|
||||
exists = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(exists == false, "Invalid exists return value")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,238 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_send_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.metadata_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_metadata_on_file_if_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.metadata_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, metadata
|
||||
f(session, { path = $file_path }, function(success, res)
|
||||
if success then
|
||||
metadata = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(metadata, "Missing metadata")
|
||||
assert(not metadata.canonicalized_path, "Unexpectedly got canonicalized path")
|
||||
assert(metadata.file_type == "file", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(metadata.len == 9, "Got wrong len: " .. metadata.len)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_metadata_on_dir_if_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.metadata_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, metadata
|
||||
f(session, { path = $dir_path }, function(success, res)
|
||||
if success then
|
||||
metadata = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(metadata, "Missing metadata")
|
||||
assert(not metadata.canonicalized_path, "Unexpectedly got canonicalized path")
|
||||
assert(metadata.file_type == "dir", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_metadata_on_symlink_if_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
let symlink = temp.child("link");
|
||||
symlink.symlink_to_file(file.path()).unwrap();
|
||||
let symlink_path = symlink.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.metadata_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, metadata
|
||||
f(session, { path = $symlink_path }, function(success, res)
|
||||
if success then
|
||||
metadata = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(metadata, "Missing metadata")
|
||||
assert(not metadata.canonicalized_path, "Unexpectedly got canonicalized path")
|
||||
assert(metadata.file_type == "symlink", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_include_canonicalized_path_if_flag_specified(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
let file_path = file.path().canonicalize().unwrap();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let symlink = temp.child("link");
|
||||
symlink.symlink_to_file(file.path()).unwrap();
|
||||
let symlink_path = symlink.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.metadata_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, metadata
|
||||
f(session, { path = $symlink_path, canonicalize = true }, function(success, res)
|
||||
if success then
|
||||
metadata = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(metadata, "Missing metadata")
|
||||
assert(
|
||||
metadata.canonicalized_path == $file_path_str,
|
||||
"Got wrong canonicalized path: " .. metadata.canonicalized_path
|
||||
)
|
||||
assert(metadata.file_type == "symlink", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_resolve_file_type_of_symlink_if_flag_specified(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
let symlink = temp.child("link");
|
||||
symlink.symlink_to_file(file.path()).unwrap();
|
||||
let symlink_path = symlink.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.metadata_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, metadata
|
||||
f(session, { path = $symlink_path, resolve_file_type = true }, function(success, res)
|
||||
if success then
|
||||
metadata = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(metadata, "Missing metadata")
|
||||
assert(metadata.file_type == "file", "Got wrong file type: " .. metadata.file_type)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
mod append_file;
|
||||
mod append_file_text;
|
||||
mod copy;
|
||||
mod create_dir;
|
||||
mod exists;
|
||||
mod metadata;
|
||||
mod read_dir;
|
||||
mod read_file;
|
||||
mod read_file_text;
|
||||
mod remove;
|
||||
mod rename;
|
||||
mod spawn;
|
||||
mod spawn_pty;
|
||||
mod spawn_wait;
|
||||
mod system_info;
|
||||
mod write_file;
|
||||
mod write_file_text;
|
@ -1,357 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
// /root/
|
||||
// /root/file1
|
||||
// /root/link1 -> /root/sub1/file2
|
||||
// /root/sub1/
|
||||
// /root/sub1/file2
|
||||
fn setup_dir() -> assert_fs::TempDir {
|
||||
let root_dir = assert_fs::TempDir::new().unwrap();
|
||||
root_dir.child("file1").touch().unwrap();
|
||||
|
||||
let sub1 = root_dir.child("sub1");
|
||||
sub1.create_dir_all().unwrap();
|
||||
|
||||
let file2 = sub1.child("file2");
|
||||
file2.touch().unwrap();
|
||||
|
||||
let link1 = root_dir.child("link1");
|
||||
link1.symlink_to_file(file2.path()).unwrap();
|
||||
|
||||
root_dir
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_directory_does_not_exist(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("test-dir");
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $dir_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_have_depth_default_to_1(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, tbl
|
||||
f(session, { path = $root_dir_path }, function(success, res)
|
||||
if success then
|
||||
tbl = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(tbl, "Missing result")
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "link1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_depth_limits(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, tbl
|
||||
f(session, { path = $root_dir_path, depth = 1 }, function(success, res)
|
||||
if success then
|
||||
tbl = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(tbl, "Missing result")
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "link1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_unlimited_depth_using_zero(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, tbl
|
||||
f(session, { path = $root_dir_path, depth = 0 }, function(success, res)
|
||||
if success then
|
||||
tbl = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(tbl, "Missing result")
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "link1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[4].file_type == "file", "Wrong file type: " .. entries[4].file_type)
|
||||
assert(entries[4].path == "sub1/file2", "Wrong path: " .. entries[4].path)
|
||||
assert(entries[4].depth == 2, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_including_directory_in_returned_entries(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
let root_dir_canonicalized_path = root_dir.path().canonicalize().unwrap();
|
||||
let root_dir_canonicalized_path_str = root_dir_canonicalized_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, tbl
|
||||
f(session, { path = $root_dir_path, include_root = true }, function(success, res)
|
||||
if success then
|
||||
tbl = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(tbl, "Missing result")
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "dir", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == $root_dir_canonicalized_path_str, "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 0, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "file", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "file1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "symlink", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "link1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[4].file_type == "dir", "Wrong file type: " .. entries[4].file_type)
|
||||
assert(entries[4].path == "sub1", "Wrong path: " .. entries[4].path)
|
||||
assert(entries[4].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_returning_absolute_paths(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let root_dir_canonicalized_path = root_dir.path().canonicalize().unwrap();
|
||||
let file1_path = root_dir_canonicalized_path.join("file1");
|
||||
let link1_path = root_dir_canonicalized_path.join("link1");
|
||||
let sub1_path = root_dir_canonicalized_path.join("sub1");
|
||||
|
||||
let file1_path_str = file1_path.to_str().unwrap();
|
||||
let link1_path_str = link1_path.to_str().unwrap();
|
||||
let sub1_path_str = sub1_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, tbl
|
||||
f(session, { path = $root_dir_path, absolute = true }, function(success, res)
|
||||
if success then
|
||||
tbl = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(tbl, "Missing result")
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == $file1_path_str, "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == $link1_path_str, "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == $sub1_path_str, "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_returning_canonicalized_paths(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_dir_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, tbl
|
||||
f(session, { path = $root_dir_path, canonicalize = true }, function(success, res)
|
||||
if success then
|
||||
tbl = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(tbl, "Missing result")
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "sub1/file2", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_fails_to_read_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_file_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path_str }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_file_contents_as_byte_list(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("abcd").unwrap();
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_file_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, contents
|
||||
f(session, { path = $file_path_str }, function(success, res)
|
||||
if success then
|
||||
contents = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(contents, "Missing file contents")
|
||||
|
||||
// abcd -> {97, 98, 99, 100}
|
||||
assert(type(contents) == "table", "Wrong content type: " .. type(contents))
|
||||
assert(contents[1] == 97, "Unexpected first byte: " .. contents[1])
|
||||
assert(contents[2] == 98, "Unexpected second byte: " .. contents[2])
|
||||
assert(contents[3] == 99, "Unexpected third byte: " .. contents[3])
|
||||
assert(contents[4] == 100, "Unexpected fourth byte: " .. contents[4])
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_fails_to_read_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_file_text_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path_str }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_file_contents_as_byte_list(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("some file contents").unwrap();
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local contents = session:read_file({ path = $file_path_str })
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.read_file_text_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, contents
|
||||
f(session, { path = $file_path_str }, function(success, res)
|
||||
if success then
|
||||
contents = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(contents, "Missing file contents")
|
||||
assert(contents == "some file contents", "Unexpected file contents: " .. contents)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.remove_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_deleting_a_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.remove_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $dir_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
dir.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_delete_nonempty_directory_if_force_is_true(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
dir.child("file").touch().unwrap();
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.remove_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $dir_path, force = true }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
dir.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_deleting_a_single_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("some-file");
|
||||
file.touch().unwrap();
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.remove_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
let dst = temp.child("dst");
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.rename_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that destination does not exist
|
||||
dst.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_renaming_an_entire_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let src_file = src.child("file");
|
||||
src_file.write_str("some contents").unwrap();
|
||||
|
||||
let dst = temp.child("dst");
|
||||
let dst_file = dst.child("file");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.rename_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we moved the contents
|
||||
src.assert(predicate::path::missing());
|
||||
src_file.assert(predicate::path::missing());
|
||||
dst.assert(predicate::path::is_dir());
|
||||
dst_file.assert("some contents");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_renaming_a_single_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.write_str("some text").unwrap();
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.rename_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { src = $src_path, dst = $dst_path }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we moved the file
|
||||
src.assert(predicate::path::missing());
|
||||
dst.assert("some text");
|
||||
}
|
@ -1,443 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use once_cell::sync::Lazy;
|
||||
use rstest::*;
|
||||
|
||||
static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> = Lazy::new(|| assert_fs::TempDir::new().unwrap());
|
||||
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
||||
|
||||
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*" 1>&2
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_STDIN_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_stdin_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
while IFS= read; do echo "$REPLY"; done
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static SLEEP_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("sleep.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#!/usr/bin/env bash
|
||||
sleep "$1"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
||||
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = DOES_NOT_EXIST_BIN.to_str().unwrap().to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_back_process_on_success(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc.id >= 0, "Invalid process returned")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stdout"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.read_stdout_async, $schedule_fn)
|
||||
local err, stdout
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stdout = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed reading stdout: " .. tostring(err))
|
||||
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some stdout", "Unexpected stdout: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stderr(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stderr"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.read_stderr_async, $schedule_fn)
|
||||
local err, stderr
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stderr = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed reading stderr: " .. tostring(err))
|
||||
|
||||
stderr = string.char(unpack(stderr))
|
||||
assert(stderr == "some stderr", "Unexpected stderr: " .. stderr)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_when_killing_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.kill_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded in killing dead process")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_killing_processing(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
local f = distant.utils.wrap_async(proc.kill_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to kill process: " .. tostring(err))
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_sending_stdin_to_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.write_stdin_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, "some text\n", function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_support_sending_stdin_to_spawned_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed spawning process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
local f = distant.utils.wrap_async(proc.write_stdin_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, "some text\n", function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed writing stdin: " .. tostring(err))
|
||||
|
||||
local f = distant.utils.wrap_async(proc.read_stdout_async, $schedule_fn)
|
||||
local err, stdout
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stdout = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed reading stdout: " .. tostring(err))
|
||||
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some text\n", "Unexpected stdout received: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,519 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use once_cell::sync::Lazy;
|
||||
use rstest::*;
|
||||
|
||||
static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> = Lazy::new(|| assert_fs::TempDir::new().unwrap());
|
||||
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
||||
|
||||
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*" 1>&2
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_STDIN_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_stdin_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
while IFS= read; do echo "$REPLY"; done
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static SLEEP_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("sleep.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#!/usr/bin/env bash
|
||||
sleep "$1"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
||||
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = DOES_NOT_EXIST_BIN.to_str().unwrap().to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_back_process_on_success(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc.id >= 0, "Invalid process returned")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stdout"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.read_stdout_async, $schedule_fn)
|
||||
local err, stdout
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stdout = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed reading stdout: " .. tostring(err))
|
||||
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some stdout", "Unexpected stdout: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stderr_as_part_of_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stderr"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
// stderr is a broken pipe as it does not exist for pty
|
||||
local f = distant.utils.wrap_async(proc.read_stderr_async, $schedule_fn)
|
||||
local err, stderr
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stderr = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err)
|
||||
|
||||
// in a pty process, stderr is part of stdout
|
||||
local f = distant.utils.wrap_async(proc.read_stdout_async, $schedule_fn)
|
||||
local err, stdout
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stdout = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed reading stdout: " .. tostring(err))
|
||||
|
||||
// stdout should match what we'd normally expect from stderr
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some stderr", "Unexpected stdout: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_when_killing_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.kill_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded in killing dead process")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_killing_processing(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
local f = distant.utils.wrap_async(proc.kill_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to kill process: " .. tostring(err))
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_sending_stdin_to_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.write_stdin_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, "some text\n", function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_support_sending_stdin_to_spawned_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed spawning process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
local f = distant.utils.wrap_async(proc.write_stdin_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, "some text\n", function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed writing stdin: " .. tostring(err))
|
||||
|
||||
// Wait briefly to ensure that pty reflects everything
|
||||
$wait_fn()
|
||||
|
||||
local f = distant.utils.wrap_async(proc.read_stdout_async, $schedule_fn)
|
||||
local err, stdout
|
||||
f(proc, function(success, res)
|
||||
if success then
|
||||
stdout = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed reading stdout: " .. tostring(err))
|
||||
|
||||
// NOTE: We're removing whitespace as there's some issue with properly comparing
|
||||
// due to something else being captured from pty
|
||||
stdout = string.gsub(string.char(unpack(stdout)), "%s+", "")
|
||||
|
||||
// TODO: Sometimes this comes back as "sometextsometext" (double) and I'm assuming
|
||||
// this is part of pty output, but the tests seem to have a race condition
|
||||
// to produce it, so we're just checking for either right now
|
||||
assert(
|
||||
stdout == "sometext" or stdout == "sometextsometext",
|
||||
"Unexpected stdout received: " .. stdout
|
||||
)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_support_resizing_pty(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, proc
|
||||
f(session, { cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } }, function(success, res)
|
||||
if success then
|
||||
proc = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed spawning process: " .. tostring(err))
|
||||
assert(proc, "Missing proc")
|
||||
|
||||
local f = distant.utils.wrap_async(proc.resize_async, $schedule_fn)
|
||||
local err
|
||||
f(proc, { rows = 16, cols = 40 }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed resizing proc: " .. tostring(err))
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use once_cell::sync::Lazy;
|
||||
use rstest::*;
|
||||
|
||||
static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> = Lazy::new(|| assert_fs::TempDir::new().unwrap());
|
||||
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
||||
|
||||
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*" 1>&2
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
||||
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = DOES_NOT_EXIST_BIN.to_str().unwrap().to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_wait_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_back_status_on_success(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_wait_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, output
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
output = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(output, "Missing process output")
|
||||
assert(output.success, "Process output returned !success")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_capture_all_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stdout"),
|
||||
];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_wait_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, output
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
output = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(output, "Missing process output")
|
||||
assert(output.stdout, "some stdout")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_capture_all_stderr(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stderr"),
|
||||
];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local distant = require("distant_lua")
|
||||
local f = distant.utils.wrap_async(session.spawn_wait_async, $schedule_fn)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, output
|
||||
f(session, { cmd = $cmd, args = $args }, function(success, res)
|
||||
if success then
|
||||
output = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed to spawn process: " .. tostring(err))
|
||||
assert(output, "Missing process output")
|
||||
assert(output.stderr, "some stderr")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_system_information(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.system_info_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err, system_info
|
||||
f(session, function(success, res)
|
||||
if success then
|
||||
system_info = res
|
||||
else
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
assert(system_info, "Missing system information")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.write_file_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, data = $data }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_overwrite_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.write_file_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, data = $data }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we overwrite the file
|
||||
file.assert("some text");
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, poll, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.write_file_text_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, text = $text }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(err, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_overwrite_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
let schedule_fn = poll::make_function(&lua).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local f = require("distant_lua").utils.wrap_async(
|
||||
session.write_file_text_async,
|
||||
$schedule_fn
|
||||
)
|
||||
|
||||
// Because of our scheduler, the invocation turns async -> sync
|
||||
local err
|
||||
f(session, { path = $file_path, text = $text }, function(success, res)
|
||||
if not success then
|
||||
err = res
|
||||
end
|
||||
end)
|
||||
assert(not err, "Unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("some text");
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
mod r#async;
|
||||
mod sync;
|
@ -1,60 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.append_file, session, {
|
||||
path = $file_path,
|
||||
data = $data
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_append_data_to_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:append_file({
|
||||
path = $file_path,
|
||||
data = $data
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("line 1some text");
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.append_file_text, session, {
|
||||
path = $file_path,
|
||||
text = $text
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_append_text_to_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:append_file_text({
|
||||
path = $file_path,
|
||||
text = $text
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("line 1some text");
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_send_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.copy, session, {
|
||||
src = $src_path,
|
||||
dst = $dst_path
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that destination does not exist
|
||||
dst.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_an_entire_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let src_file = src.child("file");
|
||||
src_file.write_str("some contents").unwrap();
|
||||
|
||||
let dst = temp.child("dst");
|
||||
let dst_file = dst.child("file");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:copy({
|
||||
src = $src_path,
|
||||
dst = $dst_path
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we have source and destination directories and associated contents
|
||||
src.assert(predicate::path::is_dir());
|
||||
src_file.assert(predicate::path::is_file());
|
||||
dst.assert(predicate::path::is_dir());
|
||||
dst_file.assert(predicate::path::eq_file(src_file.path()));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_an_empty_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:copy({
|
||||
src = $src_path,
|
||||
dst = $dst_path
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we still have source and destination directories
|
||||
src.assert(predicate::path::is_dir());
|
||||
dst.assert(predicate::path::is_dir());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_a_directory_that_only_contains_directories(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let src_dir = src.child("dir");
|
||||
src_dir.create_dir_all().unwrap();
|
||||
|
||||
let dst = temp.child("dst");
|
||||
let dst_dir = dst.child("dir");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:copy({
|
||||
src = $src_path,
|
||||
dst = $dst_path
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we have source and destination directories and associated contents
|
||||
src.assert(predicate::path::is_dir().name("src"));
|
||||
src_dir.assert(predicate::path::is_dir().name("src/dir"));
|
||||
dst.assert(predicate::path::is_dir().name("dst"));
|
||||
dst_dir.assert(predicate::path::is_dir().name("dst/dir"));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_copying_a_single_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.write_str("some text").unwrap();
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:copy({
|
||||
src = $src_path,
|
||||
dst = $dst_path
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we still have source and that destination has source's contents
|
||||
src.assert(predicate::path::is_file());
|
||||
dst.assert(predicate::path::eq_file(src.path()));
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
// /root/
|
||||
// /root/file1
|
||||
// /root/link1 -> /root/sub1/file2
|
||||
// /root/sub1/
|
||||
// /root/sub1/file2
|
||||
fn setup_dir() -> assert_fs::TempDir {
|
||||
let root_dir = assert_fs::TempDir::new().unwrap();
|
||||
root_dir.child("file1").touch().unwrap();
|
||||
|
||||
let sub1 = root_dir.child("sub1");
|
||||
sub1.create_dir_all().unwrap();
|
||||
|
||||
let file2 = sub1.child("file2");
|
||||
file2.touch().unwrap();
|
||||
|
||||
let link1 = root_dir.child("link1");
|
||||
link1.symlink_to_file(file2.path()).unwrap();
|
||||
|
||||
root_dir
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_send_error_if_fails(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Make a path that has multiple non-existent components
|
||||
// so the creation will fail
|
||||
let root_dir = setup_dir();
|
||||
let path = root_dir.path().join("nested").join("new-dir");
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.create_dir, session, { path = $path_str })
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that the directory was not actually created
|
||||
assert!(!path.exists(), "Path unexpectedly exists");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_send_ok_when_successful(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let root_dir = setup_dir();
|
||||
let path = root_dir.path().join("new-dir");
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:create_dir({ path = $path_str })
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that the directory was actually created
|
||||
assert!(path.exists(), "Directory not created");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_creating_multiple_dir_components(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let root_dir = setup_dir();
|
||||
let path = root_dir.path().join("nested").join("new-dir");
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:create_dir({ path = $path_str, all = true })
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that the directory was actually created
|
||||
assert!(path.exists(), "Directory not created");
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_send_true_if_path_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.touch().unwrap();
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local exists = session:exists({ path = $file_path })
|
||||
assert(exists, "File unexpectedly missing")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_send_false_if_path_does_not_exist(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local exists = session:exists({ path = $file_path })
|
||||
assert(not exists, "File unexpectedly found")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_send_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.metadata, session, { path = $file_path })
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_metadata_on_file_if_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local metadata = session:metadata({ path = $file_path })
|
||||
assert(not metadata.canonicalized_path, "Unexpectedly got canonicalized path")
|
||||
assert(metadata.file_type == "file", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(metadata.len == 9, "Got wrong len: " .. metadata.len)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_metadata_on_dir_if_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local metadata = session:metadata({ path = $dir_path })
|
||||
assert(not metadata.canonicalized_path, "Unexpectedly got canonicalized path")
|
||||
assert(metadata.file_type == "dir", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_metadata_on_symlink_if_exists(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
let symlink = temp.child("link");
|
||||
symlink.symlink_to_file(file.path()).unwrap();
|
||||
let symlink_path = symlink.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local metadata = session:metadata({ path = $symlink_path })
|
||||
assert(not metadata.canonicalized_path, "Unexpectedly got canonicalized path")
|
||||
assert(metadata.file_type == "symlink", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_include_canonicalized_path_if_flag_specified(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
let file_path = file.path().canonicalize().unwrap();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let symlink = temp.child("link");
|
||||
symlink.symlink_to_file(file.path()).unwrap();
|
||||
let symlink_path = symlink.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local metadata = session:metadata({
|
||||
path = $symlink_path,
|
||||
canonicalize = true,
|
||||
})
|
||||
assert(
|
||||
metadata.canonicalized_path == $file_path_str,
|
||||
"Got wrong canonicalized path: " .. metadata.canonicalized_path
|
||||
)
|
||||
assert(metadata.file_type == "symlink", "Got wrong file type: " .. metadata.file_type)
|
||||
assert(not metadata.readonly, "Unexpectedly readonly")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_resolve_file_type_of_symlink_if_flag_specified(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
let symlink = temp.child("link");
|
||||
symlink.symlink_to_file(file.path()).unwrap();
|
||||
let symlink_path = symlink.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local metadata = session:metadata({
|
||||
path = $symlink_path,
|
||||
resolve_file_type = true,
|
||||
})
|
||||
assert(metadata.file_type == "file", "Got wrong file type: " .. metadata.file_type)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
mod append_file;
|
||||
mod append_file_text;
|
||||
mod copy;
|
||||
mod create_dir;
|
||||
mod exists;
|
||||
mod metadata;
|
||||
mod read_dir;
|
||||
mod read_file;
|
||||
mod read_file_text;
|
||||
mod remove;
|
||||
mod rename;
|
||||
mod spawn;
|
||||
mod spawn_pty;
|
||||
mod spawn_wait;
|
||||
mod system_info;
|
||||
mod write_file;
|
||||
mod write_file_text;
|
@ -1,249 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
// /root/
|
||||
// /root/file1
|
||||
// /root/link1 -> /root/sub1/file2
|
||||
// /root/sub1/
|
||||
// /root/sub1/file2
|
||||
fn setup_dir() -> assert_fs::TempDir {
|
||||
let root_dir = assert_fs::TempDir::new().unwrap();
|
||||
root_dir.child("file1").touch().unwrap();
|
||||
|
||||
let sub1 = root_dir.child("sub1");
|
||||
sub1.create_dir_all().unwrap();
|
||||
|
||||
let file2 = sub1.child("file2");
|
||||
file2.touch().unwrap();
|
||||
|
||||
let link1 = root_dir.child("link1");
|
||||
link1.symlink_to_file(file2.path()).unwrap();
|
||||
|
||||
root_dir
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_directory_does_not_exist(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("test-dir");
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.read_dir, session, { path = $dir_path })
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_have_depth_default_to_1(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local tbl = session:read_dir({ path = $root_dir_path })
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "link1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_depth_limits(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local tbl = session:read_dir({ path = $root_dir_path, depth = 1 })
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "link1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_unlimited_depth_using_zero(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local tbl = session:read_dir({ path = $root_dir_path, depth = 0 })
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "link1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[4].file_type == "file", "Wrong file type: " .. entries[4].file_type)
|
||||
assert(entries[4].path == "sub1/file2", "Wrong path: " .. entries[4].path)
|
||||
assert(entries[4].depth == 2, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_including_directory_in_returned_entries(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
let root_dir_canonicalized_path = root_dir.path().canonicalize().unwrap();
|
||||
let root_dir_canonicalized_path_str = root_dir_canonicalized_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local tbl = session:read_dir({ path = $root_dir_path, include_root = true })
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "dir", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == $root_dir_canonicalized_path_str, "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 0, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "file", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "file1", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "symlink", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "link1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[4].file_type == "dir", "Wrong file type: " .. entries[4].file_type)
|
||||
assert(entries[4].path == "sub1", "Wrong path: " .. entries[4].path)
|
||||
assert(entries[4].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_returning_absolute_paths(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let root_dir_canonicalized_path = root_dir.path().canonicalize().unwrap();
|
||||
let file1_path = root_dir_canonicalized_path.join("file1");
|
||||
let link1_path = root_dir_canonicalized_path.join("link1");
|
||||
let sub1_path = root_dir_canonicalized_path.join("sub1");
|
||||
|
||||
let file1_path_str = file1_path.to_str().unwrap();
|
||||
let link1_path_str = link1_path.to_str().unwrap();
|
||||
let sub1_path_str = sub1_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local tbl = session:read_dir({ path = $root_dir_path, absolute = true })
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == $file1_path_str, "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == $link1_path_str, "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == $sub1_path_str, "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_returning_canonicalized_paths(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create directory with some nested items
|
||||
let root_dir = setup_dir();
|
||||
let root_dir_path = root_dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local tbl = session:read_dir({ path = $root_dir_path, canonicalize = true })
|
||||
|
||||
local entries = tbl.entries
|
||||
assert(entries[1].file_type == "file", "Wrong file type: " .. entries[1].file_type)
|
||||
assert(entries[1].path == "file1", "Wrong path: " .. entries[1].path)
|
||||
assert(entries[1].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[2].file_type == "symlink", "Wrong file type: " .. entries[2].file_type)
|
||||
assert(entries[2].path == "sub1/file2", "Wrong path: " .. entries[2].path)
|
||||
assert(entries[2].depth == 1, "Wrong depth")
|
||||
|
||||
assert(entries[3].file_type == "dir", "Wrong file type: " .. entries[3].file_type)
|
||||
assert(entries[3].path == "sub1", "Wrong path: " .. entries[3].path)
|
||||
assert(entries[3].depth == 1, "Wrong depth")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_fails_to_read_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.read_file, session, { path = $file_path_str })
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_file_contents_as_byte_list(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("abcd").unwrap();
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local contents = session:read_file({ path = $file_path_str })
|
||||
|
||||
// abcd -> {97, 98, 99, 100}
|
||||
assert(type(contents) == "table", "Wrong content type: " .. type(contents))
|
||||
assert(contents[1] == 97, "Unexpected first byte: " .. contents[1])
|
||||
assert(contents[2] == 98, "Unexpected second byte: " .. contents[2])
|
||||
assert(contents[3] == 99, "Unexpected third byte: " .. contents[3])
|
||||
assert(contents[4] == 100, "Unexpected fourth byte: " .. contents[4])
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_fails_to_read_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.read_file_text, session, { path = $file_path_str })
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_file_contents_as_text(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("some file contents").unwrap();
|
||||
let file_path = file.path();
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local contents = session:read_file_text({ path = $file_path_str })
|
||||
assert(contents == "some file contents", "Unexpected file contents: " .. contents)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.remove, session, { path = $file_path })
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_deleting_a_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:remove({ path = $dir_path })
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
dir.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_delete_nonempty_directory_if_force_is_true(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
dir.child("file").touch().unwrap();
|
||||
let dir_path = dir.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:remove({ path = $dir_path, force = true })
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
dir.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_deleting_a_single_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("some-file");
|
||||
file.touch().unwrap();
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:remove({ path = $file_path })
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that path does not exist
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
let dst = temp.child("dst");
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.rename, session, {
|
||||
src = $src_path,
|
||||
dst = $dst_path,
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also, verify that destination does not exist
|
||||
dst.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_renaming_an_entire_directory(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let src = temp.child("src");
|
||||
src.create_dir_all().unwrap();
|
||||
let src_file = src.child("file");
|
||||
src_file.write_str("some contents").unwrap();
|
||||
|
||||
let dst = temp.child("dst");
|
||||
let dst_file = dst.child("file");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:rename({
|
||||
src = $src_path,
|
||||
dst = $dst_path,
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we moved the contents
|
||||
src.assert(predicate::path::missing());
|
||||
src_file.assert(predicate::path::missing());
|
||||
dst.assert(predicate::path::is_dir());
|
||||
dst_file.assert("some contents");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_renaming_a_single_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let src = temp.child("src");
|
||||
src.write_str("some text").unwrap();
|
||||
let dst = temp.child("dst");
|
||||
|
||||
let src_path = src.path().to_str().unwrap();
|
||||
let dst_path = dst.path().to_str().unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:rename({
|
||||
src = $src_path,
|
||||
dst = $dst_path,
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Verify that we moved the file
|
||||
src.assert(predicate::path::missing());
|
||||
dst.assert("some text");
|
||||
}
|
@ -1,291 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use once_cell::sync::Lazy;
|
||||
use rstest::*;
|
||||
|
||||
static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> = Lazy::new(|| assert_fs::TempDir::new().unwrap());
|
||||
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
||||
|
||||
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*" 1>&2
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_STDIN_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_stdin_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
while IFS= read; do echo "$REPLY"; done
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static SLEEP_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("sleep.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#!/usr/bin/env bash
|
||||
sleep "$1"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
||||
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = DOES_NOT_EXIST_BIN.to_str().unwrap().to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.spawn, session, {
|
||||
cmd = $cmd,
|
||||
args = $args
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_back_process_on_success(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
assert(proc.id >= 0, "Invalid process returned")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stdout"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local stdout = proc:read_stdout()
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some stdout", "Unexpected stdout: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stderr(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stderr"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local stderr = proc:read_stderr()
|
||||
stderr = string.char(unpack(stderr))
|
||||
assert(stderr == "some stderr", "Unexpected stderr: " .. stderr)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_when_killing_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local status, _ = pcall(proc.kill, proc)
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_killing_processing(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
proc:kill()
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_sending_stdin_to_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local status, _ = pcall(proc.write_stdin, proc, "some text")
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_support_sending_stdin_to_spawned_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args })
|
||||
proc:write_stdin("some text\n")
|
||||
|
||||
// Wait briefly to ensure the process echoes stdin
|
||||
$wait_fn()
|
||||
|
||||
local stdout = proc:read_stdout()
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some text\n", "Unexpected stdin sent: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,324 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use once_cell::sync::Lazy;
|
||||
use rstest::*;
|
||||
|
||||
static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> = Lazy::new(|| assert_fs::TempDir::new().unwrap());
|
||||
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
||||
|
||||
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*" 1>&2
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_STDIN_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_stdin_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
while IFS= read; do echo "$REPLY"; done
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static SLEEP_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("sleep.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#!/usr/bin/env bash
|
||||
sleep "$1"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
||||
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = DOES_NOT_EXIST_BIN.to_str().unwrap().to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.spawn, session, {
|
||||
cmd = $cmd,
|
||||
args = $args,
|
||||
pty = { rows = 24, cols = 80 }
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_back_process_on_success(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, pty = { rows = 24, cols = 80 } })
|
||||
assert(proc.id >= 0, "Invalid process returned")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stdout"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local stdout = proc:read_stdout()
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some stdout", "Unexpected stdout: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_return_process_that_can_retrieve_stderr(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stderr"),
|
||||
];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
|
||||
// Wait briefly to ensure the process sends stdout
|
||||
$wait_fn()
|
||||
|
||||
local stderr = proc:read_stderr()
|
||||
stderr = string.char(unpack(stderr))
|
||||
assert(stderr == "some stderr", "Unexpected stderr: " .. stderr)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_when_killing_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local status, _ = pcall(proc.kill, proc)
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_killing_processing(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
proc:kill()
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_if_sending_stdin_to_dead_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Spawn a process that will exit immediately, but is a valid process
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0")];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
|
||||
// Wait briefly to ensure the process dies
|
||||
$wait_fn()
|
||||
|
||||
local status, _ = pcall(proc.write_stdin, proc, "some text")
|
||||
assert(not status, "Unexpectedly succeeded")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_support_sending_stdin_to_spawned_process(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
proc:write_stdin("some text\n")
|
||||
|
||||
// Wait briefly to ensure the process echoes stdin
|
||||
$wait_fn()
|
||||
|
||||
local stdout = proc:read_stdout()
|
||||
stdout = string.char(unpack(stdout))
|
||||
assert(stdout == "some text\n", "Unexpected stdin sent: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_support_resizing_pty(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let wait_fn = lua
|
||||
.create_function(|_, ()| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local proc = session:spawn({ cmd = $cmd, args = $args, rows = 24, cols = 80 })
|
||||
|
||||
// Wait briefly to ensure the process starts
|
||||
$wait_fn()
|
||||
|
||||
proc:resize({ rows = 16, cols = 40 })
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use once_cell::sync::Lazy;
|
||||
use rstest::*;
|
||||
|
||||
static TEMP_SCRIPT_DIR: Lazy<assert_fs::TempDir> = Lazy::new(|| assert_fs::TempDir::new().unwrap());
|
||||
static SCRIPT_RUNNER: Lazy<String> = Lazy::new(|| String::from("bash"));
|
||||
|
||||
static ECHO_ARGS_TO_STDOUT_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stdout.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*"
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static ECHO_ARGS_TO_STDERR_SH: Lazy<assert_fs::fixture::ChildPath> = Lazy::new(|| {
|
||||
let script = TEMP_SCRIPT_DIR.child("echo_args_to_stderr.sh");
|
||||
script
|
||||
.write_str(indoc::indoc!(
|
||||
r#"
|
||||
#/usr/bin/env bash
|
||||
printf "%s" "$*" 1>&2
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
script
|
||||
});
|
||||
|
||||
static DOES_NOT_EXIST_BIN: Lazy<assert_fs::fixture::ChildPath> =
|
||||
Lazy::new(|| TEMP_SCRIPT_DIR.child("does_not_exist_bin"));
|
||||
|
||||
#[rstest]
|
||||
fn should_return_error_on_failure(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = DOES_NOT_EXIST_BIN.to_str().unwrap().to_string();
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.spawn_wait, session, {
|
||||
cmd = $cmd,
|
||||
args = $args
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_return_back_process_on_success(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local output = session:spawn_wait({ cmd = $cmd, args = $args })
|
||||
assert(output, "Missing process output")
|
||||
assert(output.success, "Process unexpectedly failed")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_capture_all_stdout(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stdout"),
|
||||
];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local output = session:spawn_wait({ cmd = $cmd, args = $args })
|
||||
assert(output, "Missing process output")
|
||||
assert(output.success, "Process unexpectedly failed")
|
||||
|
||||
local stdout, stderr
|
||||
stdout = string.char(unpack(output.stdout))
|
||||
stderr = string.char(unpack(output.stderr))
|
||||
|
||||
assert(stdout == "some stdout", "Unexpected stdout: " .. stdout)
|
||||
assert(stderr == "", "Unexpected stderr: " .. stderr)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
||||
|
||||
// NOTE: Ignoring on windows because it's using WSL which wants a Linux path
|
||||
// with / but thinks it's on windows and is providing \
|
||||
#[rstest]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn should_capture_all_stderr(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let cmd = SCRIPT_RUNNER.to_string();
|
||||
let args = vec![
|
||||
ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(),
|
||||
String::from("some stderr"),
|
||||
];
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local output = session:spawn_wait({ cmd = $cmd, args = $args })
|
||||
assert(output, "Missing process output")
|
||||
assert(output.success, "Process unexpectedly failed")
|
||||
|
||||
local stdout, stderr
|
||||
stdout = string.char(unpack(output.stdout))
|
||||
stderr = string.char(unpack(output.stderr))
|
||||
|
||||
assert(stdout == "", "Unexpected stdout: " .. stdout)
|
||||
assert(stderr == "some stderr", "Unexpected stderr: " .. stderr)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use mlua::chunk;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_return_system_info(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local system_info = session:system_info()
|
||||
assert(system_info, "System info unexpectedly missing")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.write_file, session, {
|
||||
path = $file_path,
|
||||
data = $data
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_overwrite_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let data = b"some text".to_vec();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:write_file({
|
||||
path = $file_path,
|
||||
data = $data
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we overwrite the file
|
||||
file.assert("some text");
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use crate::common::{fixtures::*, lua, session};
|
||||
use assert_fs::prelude::*;
|
||||
use mlua::chunk;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
fn should_yield_error_if_fails_to_create_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
// Create a temporary path and add to it to ensure that there are
|
||||
// extra components that don't exist to cause writing to fail
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("dir").child("test-file");
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
local status, _ = pcall(session.write_file_text, session, {
|
||||
path = $file_path,
|
||||
text = $text
|
||||
})
|
||||
assert(not status, "Unexpectedly succeeded!")
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we didn't actually create the file
|
||||
file.assert(predicate::path::missing());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_overwrite_existing_file(ctx: &'_ DistantServerCtx) {
|
||||
let lua = lua::make().unwrap();
|
||||
let new_session = session::make_function(&lua, ctx).unwrap();
|
||||
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str("line 1").unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
let text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:write_file_text({
|
||||
path = $file_path,
|
||||
text = $text
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("some text");
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
rustflags = [
|
||||
"-C", "target-feature=-crt-static",
|
||||
"-C", "linker=musl-cc",
|
||||
]
|
@ -1,41 +0,0 @@
|
||||
[package]
|
||||
name = "distant-lua"
|
||||
description = "Lua bindings to the distant Rust crates"
|
||||
categories = ["api-bindings", "network-programming"]
|
||||
keywords = ["api", "async"]
|
||||
version = "0.16.0"
|
||||
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/chipsenkbeil/distant"
|
||||
repository = "https://github.com/chipsenkbeil/distant"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["lua51", "vendored"]
|
||||
lua54 = ["mlua/lua54"]
|
||||
lua53 = ["mlua/lua53"]
|
||||
lua52 = ["mlua/lua52"]
|
||||
lua51 = ["mlua/lua51"]
|
||||
luajit = ["mlua/luajit"]
|
||||
vendored = ["mlua/vendored"]
|
||||
|
||||
[dependencies]
|
||||
distant-core = { version = "=0.16.0", path = "../distant-core" }
|
||||
distant-ssh2 = { version = "=0.16.0", features = ["serde"], path = "../distant-ssh2" }
|
||||
futures = "0.3.17"
|
||||
log = "0.4.14"
|
||||
mlua = { version = "0.7.3", features = ["async", "macros", "module", "serialize"] }
|
||||
once_cell = "1.8.0"
|
||||
oorandom = "11.1.3"
|
||||
paste = "1.0.5"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
simplelog = "0.10.2"
|
||||
tokio = { version = "1.12.0", features = ["macros", "time"] }
|
||||
whoami = "1.1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.11.0"
|
@ -1,105 +0,0 @@
|
||||
# Distant Lua (module)
|
||||
|
||||
Contains the Lua module wrapper around several distant libraries
|
||||
including:
|
||||
|
||||
1. **distant-core**
|
||||
2. **distant-ssh2**
|
||||
|
||||
## Building
|
||||
|
||||
*Compilation MUST be done within this directory! This crate depends on
|
||||
.cargo/config.toml settings, which are only used when built from within this
|
||||
directory.*
|
||||
|
||||
```bash
|
||||
# Outputs a library file (*.so for Linux, *.dylib for MacOS, *.dll for Windows)
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Rename `libdistant_lua.so` or `libdistant_lua.dylib` to `distant_lua.so`
|
||||
(yes, **.so** for **.dylib**) and place the library in your Lua path at
|
||||
the *root*. The library cannot be within any submodule otherwise it fails
|
||||
to load appropriate symbols. For neovim, this means directly within the
|
||||
`lua/` directory.
|
||||
|
||||
```lua
|
||||
local distant = require("distant_lua")
|
||||
|
||||
-- The majority of the distant lua module provides async and sync variants
|
||||
-- of methods; however, launching a session is currently only synchronous
|
||||
local session = distant.session.launch({ host = "127.0.0.1" })
|
||||
|
||||
-- Sync methods are executed in a blocking fashion, returning the result of
|
||||
-- the operation if successful or throwing an error if failing. Use `pcall`
|
||||
-- if you want to capture the error
|
||||
local success, result = pcall(session.read_dir, session, { path = "path/to/dir" })
|
||||
if success then
|
||||
for _, entry in ipairs(result.entries) do
|
||||
print("Entry", entry.file_type, entry.path, entry.depth)
|
||||
end
|
||||
else
|
||||
print(result)
|
||||
end
|
||||
|
||||
-- Async methods have _async as a suffix and need to be polled from
|
||||
-- Lua in some manner; the `wrap_async` function provides a convience
|
||||
-- to do so taking an async distant function and a scheduling function
|
||||
local schedule_fn = function(cb) end
|
||||
local read_dir = distant.utils.wrap_async(session.read_dir_async, schedule_fn)
|
||||
read_dir(session, { path = "path/to/dir" }, function(success, result)
|
||||
-- success: Returns true if ok and false if err
|
||||
-- result: If success is true, then is the resulting value,
|
||||
-- otherwise is the error
|
||||
print("Success", success)
|
||||
if success then
|
||||
for _, entry in ipairs(result.entries) do
|
||||
print("Entry", entry.file_type, entry.path, entry.depth)
|
||||
end
|
||||
else
|
||||
print(result)
|
||||
end
|
||||
end)
|
||||
|
||||
-- For neovim, there exists a helper function that converts async functions
|
||||
-- into functions that take callbacks, executing the asynchronous logic
|
||||
-- using neovim's event loop powered by libuv
|
||||
local read_dir = distant.utils.nvim_wrap_async(session.read_dir_async)
|
||||
read_dir(session, { path = "path/to/dir" }, function(success, result)
|
||||
-- success: Returns true if ok and false if err
|
||||
-- result: If success is true, then is the resulting value,
|
||||
-- otherwise is the error
|
||||
print("Success", success)
|
||||
if success then
|
||||
for _, entry in ipairs(result.entries) do
|
||||
print("Entry", entry.file_type, entry.path, entry.depth)
|
||||
end
|
||||
else
|
||||
print(result)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are run in a separate crate due to linking described here:
|
||||
[khvzak/mlua#79](https://github.com/khvzak/mlua/issues/79). You **must** build
|
||||
this module prior to running the tests!
|
||||
|
||||
```bash
|
||||
# From root of repository
|
||||
(cd distant-lua-tests && cargo test --release)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under either of
|
||||
|
||||
Apache License, Version 2.0, (LICENSE-APACHE or
|
||||
[apache-license][apache-license]) MIT license (LICENSE-MIT or
|
||||
[mit-license][mit-license]) at your option.
|
||||
|
||||
[apache-license]: http://www.apache.org/licenses/LICENSE-2.0
|
||||
[mit-license]: http://opensource.org/licenses/MIT
|
@ -1,8 +0,0 @@
|
||||
/// Default timeout (15 secs)
|
||||
pub const TIMEOUT_MILLIS: u64 = 15000;
|
||||
|
||||
/// Default polling interval for internal process reading and writing
|
||||
pub const PROC_POLL_TIMEOUT: u64 = 200;
|
||||
|
||||
/// Default polling interval for neovim (0.2 secs)
|
||||
pub const NVIM_POLL_TIMEOUT: u64 = 200;
|
@ -1,58 +0,0 @@
|
||||
use mlua::prelude::*;
|
||||
|
||||
/// to_value!<'a, T: Serialize + ?Sized>(lua: &'a Lua, t: &T) -> Result<Value<'a>>
|
||||
///
|
||||
/// Converts to a Lua value using options specific to this module.
|
||||
macro_rules! to_value {
|
||||
($lua:expr, $x:expr) => {{
|
||||
use mlua::{prelude::*, LuaSerdeExt};
|
||||
let options = LuaSerializeOptions::new()
|
||||
.serialize_none_to_null(false)
|
||||
.serialize_unit_to_null(false);
|
||||
$lua.to_value_with($x, options)
|
||||
}};
|
||||
}
|
||||
|
||||
mod constants;
|
||||
mod log;
|
||||
mod runtime;
|
||||
mod session;
|
||||
mod utils;
|
||||
|
||||
#[mlua::lua_module]
|
||||
fn distant_lua(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let exports = lua.create_table()?;
|
||||
|
||||
// Provide a static pending type used when consumer wants to use async functions
|
||||
// directly without wrapping them with a scheduler
|
||||
exports.set("pending", utils::pending(lua)?)?;
|
||||
|
||||
// API modules available for users
|
||||
exports.set("log", log::make_log_tbl(lua)?)?;
|
||||
exports.set("session", session::make_session_tbl(lua)?)?;
|
||||
exports.set("utils", utils::make_utils_tbl(lua)?)?;
|
||||
exports.set("version", make_version_tbl(lua)?)?;
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
macro_rules! set_nonempty_env {
|
||||
($tbl:ident, $key:literal, $env_key:literal) => {{
|
||||
let value = env!($env_key);
|
||||
if !value.is_empty() {
|
||||
$tbl.set($key, value)?;
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn make_version_tbl(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let tbl = lua.create_table()?;
|
||||
|
||||
set_nonempty_env!(tbl, "full", "CARGO_PKG_VERSION");
|
||||
set_nonempty_env!(tbl, "major", "CARGO_PKG_VERSION_MAJOR");
|
||||
set_nonempty_env!(tbl, "minor", "CARGO_PKG_VERSION_MINOR");
|
||||
set_nonempty_env!(tbl, "patch", "CARGO_PKG_VERSION_PATCH");
|
||||
set_nonempty_env!(tbl, "pre", "CARGO_PKG_VERSION_PRE");
|
||||
|
||||
Ok(tbl)
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
use mlua::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use simplelog::{
|
||||
ColorChoice, CombinedLogger, ConfigBuilder, LevelFilter, SharedLogger, TermLogger,
|
||||
TerminalMode, WriteLogger,
|
||||
};
|
||||
use std::{fs::File, path::PathBuf};
|
||||
|
||||
macro_rules! set_log_fn {
|
||||
($lua:expr, $tbl:expr, $name:ident) => {
|
||||
$tbl.set(
|
||||
stringify!($name),
|
||||
$lua.create_function(|_, msg: String| {
|
||||
::log::$name!("{}", msg);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum LogLevel {
|
||||
Off,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
impl From<LogLevel> for LevelFilter {
|
||||
fn from(level: LogLevel) -> Self {
|
||||
match level {
|
||||
LogLevel::Off => Self::Off,
|
||||
LogLevel::Error => Self::Error,
|
||||
LogLevel::Warn => Self::Warn,
|
||||
LogLevel::Info => Self::Info,
|
||||
LogLevel::Debug => Self::Debug,
|
||||
LogLevel::Trace => Self::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
struct LogOpts {
|
||||
/// Indicating whether or not to log to terminal
|
||||
terminal: bool,
|
||||
|
||||
/// Path to file to store logs
|
||||
file: Option<PathBuf>,
|
||||
|
||||
/// Base level at which to write logs
|
||||
/// (e.g. if debug then trace would not be logged)
|
||||
level: LogLevel,
|
||||
}
|
||||
|
||||
impl Default for LogOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
terminal: false,
|
||||
file: None,
|
||||
level: LogLevel::Warn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_logger(opts: LogOpts) -> LuaResult<()> {
|
||||
let mut loggers: Vec<Box<dyn SharedLogger>> = Vec::new();
|
||||
let config = ConfigBuilder::new()
|
||||
.add_filter_allow_str("distant_core")
|
||||
.add_filter_allow_str("distant_ssh2")
|
||||
.add_filter_allow_str("distant_lua")
|
||||
.build();
|
||||
|
||||
if opts.terminal {
|
||||
loggers.push(TermLogger::new(
|
||||
opts.level.into(),
|
||||
config.clone(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(path) = opts.file {
|
||||
loggers.push(WriteLogger::new(
|
||||
opts.level.into(),
|
||||
config,
|
||||
File::create(path)?,
|
||||
));
|
||||
}
|
||||
|
||||
CombinedLogger::init(loggers).to_lua_err()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Makes a Lua table containing the log functions
|
||||
pub fn make_log_tbl(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let tbl = lua.create_table()?;
|
||||
|
||||
tbl.set(
|
||||
"init",
|
||||
lua.create_function(|lua, opts: LuaValue| init_logger(lua.from_value(opts)?))?,
|
||||
)?;
|
||||
|
||||
set_log_fn!(lua, tbl, error);
|
||||
set_log_fn!(lua, tbl, warn);
|
||||
set_log_fn!(lua, tbl, info);
|
||||
set_log_fn!(lua, tbl, debug);
|
||||
set_log_fn!(lua, tbl, trace);
|
||||
|
||||
Ok(tbl)
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use mlua::prelude::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::future::Future;
|
||||
|
||||
/// Retrieves the global runtime, initializing it if not initialized, and returning
|
||||
/// an error if failed to initialize
|
||||
pub fn get_runtime() -> LuaResult<&'static tokio::runtime::Runtime> {
|
||||
static RUNTIME: OnceCell<tokio::runtime::Runtime> = OnceCell::new();
|
||||
RUNTIME.get_or_try_init(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|x| x.to_lua_err())
|
||||
})
|
||||
}
|
||||
|
||||
/// Blocks using the global runtime for a future that returns `LuaResult<T>`
|
||||
pub fn block_on<F, T>(future: F) -> LuaResult<T>
|
||||
where
|
||||
F: Future<Output = Result<T, LuaError>>,
|
||||
{
|
||||
get_runtime()?.block_on(future)
|
||||
}
|
||||
|
||||
/// Spawns a task on the global runtime for a future that returns a `LuaResult<T>`
|
||||
pub fn spawn<F, T>(f: F) -> impl Future<Output = LuaResult<T>>
|
||||
where
|
||||
F: Future<Output = Result<T, LuaError>> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
futures::future::ready(get_runtime()).and_then(|rt| {
|
||||
rt.spawn(f).map(|result| match result {
|
||||
Ok(x) => x.to_lua_err(),
|
||||
Err(x) => Err(x).to_lua_err(),
|
||||
})
|
||||
})
|
||||
}
|
@ -1,330 +0,0 @@
|
||||
use crate::{runtime, utils};
|
||||
use distant_core::{
|
||||
SecretKey32, Session as DistantSession, SessionChannel, SessionDetails, XChaCha20Poly1305Codec,
|
||||
};
|
||||
use distant_ssh2::{IntoDistantSessionOpts, Ssh2Session};
|
||||
use log::*;
|
||||
use mlua::{prelude::*, LuaSerdeExt, UserData, UserDataFields, UserDataMethods};
|
||||
use once_cell::sync::Lazy;
|
||||
use paste::paste;
|
||||
use std::{collections::HashMap, io, sync::RwLock};
|
||||
|
||||
/// Makes a Lua table containing the session functions
|
||||
pub fn make_session_tbl(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let tbl = lua.create_table()?;
|
||||
|
||||
// get_all() -> Vec<Session>
|
||||
tbl.set("get_all", lua.create_function(|_, ()| Session::all())?)?;
|
||||
|
||||
// get_by_id(id: usize) -> Option<Session>
|
||||
tbl.set(
|
||||
"get_by_id",
|
||||
lua.create_function(|_, id: usize| {
|
||||
let exists = has_session(id)?;
|
||||
if exists {
|
||||
Ok(Some(Session::new(id)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// launch(opts: LaunchOpts) -> Session
|
||||
tbl.set(
|
||||
"launch",
|
||||
lua.create_function(|lua, opts: LuaValue| {
|
||||
let opts = LaunchOpts::from_lua(opts, lua)?;
|
||||
runtime::block_on(Session::launch(opts))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// connect_async(opts: ConnectOpts) -> Future<Session>
|
||||
tbl.set(
|
||||
"connect_async",
|
||||
lua.create_async_function(|lua, opts: LuaValue| async move {
|
||||
let opts = ConnectOpts::from_lua(opts, lua)?;
|
||||
runtime::spawn(Session::connect(opts)).await
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// connect(opts: ConnectOpts) -> Session
|
||||
tbl.set(
|
||||
"connect",
|
||||
lua.create_function(|lua, opts: LuaValue| {
|
||||
let opts = ConnectOpts::from_lua(opts, lua)?;
|
||||
runtime::block_on(Session::connect(opts))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(tbl)
|
||||
}
|
||||
|
||||
/// try_timeout!(timeout: Duration, Future<Output = Result<T, E>>) -> LuaResult<T>
|
||||
macro_rules! try_timeout {
|
||||
($timeout:expr, $f:expr) => {{
|
||||
use futures::future::FutureExt;
|
||||
use mlua::prelude::*;
|
||||
let timeout: std::time::Duration = $timeout;
|
||||
crate::runtime::spawn(async move {
|
||||
let fut = ($f).fuse();
|
||||
let sleep = tokio::time::sleep(timeout).fuse();
|
||||
|
||||
tokio::select! {
|
||||
_ = sleep => {
|
||||
let err = std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
format!("Reached timeout of {}s", timeout.as_secs_f32())
|
||||
);
|
||||
Err(err.to_lua_err())
|
||||
}
|
||||
res = fut => {
|
||||
res.to_lua_err()
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}};
|
||||
}
|
||||
|
||||
mod api;
|
||||
mod opts;
|
||||
mod proc;
|
||||
|
||||
use opts::Mode;
|
||||
pub use opts::{ConnectOpts, LaunchOpts};
|
||||
use proc::{RemoteLspProcess, RemoteProcess};
|
||||
|
||||
/// Contains mapping of id -> session for use in maintaining active sessions
|
||||
static SESSION_MAP: Lazy<RwLock<HashMap<usize, DistantSession>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
fn has_session(id: usize) -> LuaResult<bool> {
|
||||
Ok(SESSION_MAP
|
||||
.read()
|
||||
.map_err(|x| x.to_string().to_lua_err())?
|
||||
.contains_key(&id))
|
||||
}
|
||||
|
||||
fn with_session<T>(id: usize, f: impl FnOnce(&DistantSession) -> T) -> LuaResult<T> {
|
||||
let lock = SESSION_MAP.read().map_err(|x| x.to_string().to_lua_err())?;
|
||||
let session = lock.get(&id).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotConnected,
|
||||
format!("No session connected with id {}", id),
|
||||
)
|
||||
.to_lua_err()
|
||||
})?;
|
||||
|
||||
Ok(f(session))
|
||||
}
|
||||
|
||||
fn get_session_details(id: usize) -> LuaResult<Option<SessionDetails>> {
|
||||
with_session(id, |session| session.details().cloned())
|
||||
}
|
||||
|
||||
fn get_session_channel(id: usize) -> LuaResult<SessionChannel> {
|
||||
with_session(id, |session| session.clone_channel())
|
||||
}
|
||||
|
||||
/// Holds a reference to the session to perform remote operations
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Session {
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Creates a new session referencing the given distant session with the specified id
|
||||
pub fn new(id: usize) -> Self {
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// Retrieves all sessions
|
||||
pub fn all() -> LuaResult<Vec<Self>> {
|
||||
Ok(SESSION_MAP
|
||||
.read()
|
||||
.map_err(|x| x.to_string().to_lua_err())?
|
||||
.keys()
|
||||
.copied()
|
||||
.map(Self::new)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Launches a new distant session on a remote machine
|
||||
pub async fn launch(opts: LaunchOpts<'_>) -> LuaResult<Self> {
|
||||
trace!("Session::launch({:?})", opts);
|
||||
let LaunchOpts {
|
||||
host,
|
||||
mode,
|
||||
handler,
|
||||
ssh,
|
||||
distant,
|
||||
timeout,
|
||||
} = opts;
|
||||
|
||||
// First, establish a connection to an SSH server
|
||||
debug!("Connecting to {} {:#?}", host, ssh);
|
||||
let mut ssh_session = Ssh2Session::connect(host.as_str(), ssh).to_lua_err()?;
|
||||
|
||||
// Second, authenticate with the server
|
||||
debug!("Authenticating against {}", host);
|
||||
ssh_session.authenticate(handler).await.to_lua_err()?;
|
||||
|
||||
// Third, convert our ssh session into a distant session based on desired method
|
||||
debug!("Mapping session for {} into {:?}", host, mode);
|
||||
let session = match mode {
|
||||
Mode::Distant => ssh_session
|
||||
.into_distant_session(IntoDistantSessionOpts {
|
||||
binary: distant.bin,
|
||||
args: distant.args,
|
||||
use_login_shell: distant.use_login_shell,
|
||||
timeout,
|
||||
})
|
||||
.await
|
||||
.to_lua_err()?,
|
||||
Mode::Ssh => ssh_session.into_ssh_client_session().await.to_lua_err()?,
|
||||
};
|
||||
|
||||
// Fourth, store our current session in our global map and then return a reference
|
||||
let id = utils::rand_u32()? as usize;
|
||||
debug!("Session {} established against {}", id, host);
|
||||
SESSION_MAP
|
||||
.write()
|
||||
.map_err(|x| x.to_string().to_lua_err())?
|
||||
.insert(id, session);
|
||||
Ok(Self::new(id))
|
||||
}
|
||||
|
||||
/// Connects to an already-running remote distant server
|
||||
pub async fn connect(opts: ConnectOpts) -> LuaResult<Self> {
|
||||
trace!("Session::connect({:?})", opts);
|
||||
|
||||
debug!("Looking up {}", opts.host);
|
||||
let addr = tokio::net::lookup_host(format!("{}:{}", opts.host, opts.port))
|
||||
.await
|
||||
.to_lua_err()?
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::AddrNotAvailable,
|
||||
"Failed to resolve host & port",
|
||||
)
|
||||
})
|
||||
.to_lua_err()?;
|
||||
|
||||
debug!("Constructing codec");
|
||||
let key: SecretKey32 = opts.key.parse().to_lua_err()?;
|
||||
let codec = XChaCha20Poly1305Codec::from(key);
|
||||
|
||||
debug!("Connecting to {}", addr);
|
||||
let session = DistantSession::tcp_connect_timeout(addr, codec, opts.timeout)
|
||||
.await
|
||||
.to_lua_err()?;
|
||||
|
||||
let id = utils::rand_u32()? as usize;
|
||||
debug!("Session {} established against {}", id, opts.host);
|
||||
SESSION_MAP
|
||||
.write()
|
||||
.map_err(|x| x.to_string().to_lua_err())?
|
||||
.insert(id, session);
|
||||
Ok(Self::new(id))
|
||||
}
|
||||
}
|
||||
|
||||
/// impl_methods!(methods: &mut M, name: Ident)
|
||||
macro_rules! impl_methods {
|
||||
($methods:expr, $name:ident) => {
|
||||
impl_methods!($methods, $name, |_lua, data| {Ok(data)});
|
||||
};
|
||||
($methods:expr, $name:ident, |$lua:ident, $data:ident| $block:block) => {{
|
||||
paste! {
|
||||
$methods.add_method(stringify!([<$name:snake>]), |$lua, this, params: Option<LuaValue>| {
|
||||
let params: LuaValue = match params {
|
||||
Some(params) => params,
|
||||
None => LuaValue::Table($lua.create_table()?),
|
||||
};
|
||||
let params: api::[<$name:camel Params>] = $lua.from_value(params)?;
|
||||
let $data = api::[<$name:snake>](get_session_channel(this.id)?, params)?;
|
||||
|
||||
#[allow(unused_braces)]
|
||||
$block
|
||||
});
|
||||
$methods.add_async_method(stringify!([<$name:snake _async>]), |$lua, this, params: Option<LuaValue>| async move {
|
||||
let rt = crate::runtime::get_runtime()?;
|
||||
let params: LuaValue = match params {
|
||||
Some(params) => params,
|
||||
None => LuaValue::Table($lua.create_table()?),
|
||||
};
|
||||
let params: api::[<$name:camel Params>] = $lua.from_value(params)?;
|
||||
let $data = {
|
||||
let tmp = rt.spawn(
|
||||
api::[<$name:snake _async>](get_session_channel(this.id)?, params)
|
||||
).await;
|
||||
|
||||
match tmp {
|
||||
Ok(x) => x.to_lua_err(),
|
||||
Err(x) => Err(x).to_lua_err(),
|
||||
}
|
||||
}?;
|
||||
|
||||
#[allow(unused_braces)]
|
||||
$block
|
||||
});
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl UserData for Session {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, this| Ok(this.id));
|
||||
fields.add_field_method_get("details", |lua, this| {
|
||||
get_session_details(this.id).and_then(|x| to_value!(lua, &x))
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("is_active", |_, this, ()| {
|
||||
Ok(get_session_channel(this.id).is_ok())
|
||||
});
|
||||
|
||||
impl_methods!(methods, append_file);
|
||||
impl_methods!(methods, append_file_text);
|
||||
impl_methods!(methods, copy);
|
||||
impl_methods!(methods, create_dir);
|
||||
impl_methods!(methods, exists);
|
||||
impl_methods!(methods, metadata, |lua, m| { to_value!(lua, &m) });
|
||||
impl_methods!(methods, read_dir, |lua, results| {
|
||||
let (entries, errors) = results;
|
||||
let tbl = lua.create_table()?;
|
||||
tbl.set(
|
||||
"entries",
|
||||
entries
|
||||
.iter()
|
||||
.map(|x| to_value!(lua, x))
|
||||
.collect::<LuaResult<Vec<LuaValue>>>()?,
|
||||
)?;
|
||||
tbl.set(
|
||||
"errors",
|
||||
errors
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)?;
|
||||
|
||||
Ok(tbl)
|
||||
});
|
||||
impl_methods!(methods, read_file);
|
||||
impl_methods!(methods, read_file_text);
|
||||
impl_methods!(methods, remove);
|
||||
impl_methods!(methods, rename);
|
||||
impl_methods!(methods, spawn, |_lua, proc| {
|
||||
Ok(RemoteProcess::from_distant(proc))
|
||||
});
|
||||
impl_methods!(methods, spawn_wait);
|
||||
impl_methods!(methods, spawn_lsp, |_lua, proc| {
|
||||
Ok(RemoteLspProcess::from_distant(proc))
|
||||
});
|
||||
impl_methods!(methods, system_info, |lua, info| { lua.to_value(&info) });
|
||||
impl_methods!(methods, write_file);
|
||||
impl_methods!(methods, write_file_text);
|
||||
}
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
use crate::{
|
||||
runtime,
|
||||
session::proc::{Output, RemoteProcess as LuaRemoteProcess},
|
||||
};
|
||||
use distant_core::{
|
||||
data::PtySize, DirEntry, Error as Failure, Metadata, RemoteLspProcess, RemoteProcess,
|
||||
SessionChannel, SessionChannelExt, SystemInfo,
|
||||
};
|
||||
use mlua::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use paste::paste;
|
||||
use serde::Deserialize;
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
static TENANT: Lazy<String> = Lazy::new(whoami::hostname);
|
||||
|
||||
/// Default depth for reading directory
|
||||
const fn default_depth() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
// Default timeout in milliseconds (15 secs)
|
||||
const fn default_timeout() -> u64 {
|
||||
15000
|
||||
}
|
||||
|
||||
macro_rules! make_api {
|
||||
(
|
||||
$name:ident,
|
||||
$ret:ty,
|
||||
$({$($(#[$pmeta:meta])* $pname:ident: $ptype:ty),+},)?
|
||||
|$channel:ident, $tenant:ident, $params:ident| $block:block $(,)?
|
||||
) => {
|
||||
paste! {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct [<$name:camel Params>] {
|
||||
$($($(#[$pmeta])* $pname: $ptype,)*)?
|
||||
|
||||
#[serde(default = "default_timeout")]
|
||||
timeout: u64,
|
||||
}
|
||||
|
||||
impl [<$name:camel Params>] {
|
||||
fn to_timeout_duration(&self) -> Duration {
|
||||
Duration::from_millis(self.timeout)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn [<$name:snake>](
|
||||
channel: SessionChannel,
|
||||
params: [<$name:camel Params>],
|
||||
) -> LuaResult<$ret> {
|
||||
runtime::block_on([<$name:snake _async>](channel, params))
|
||||
}
|
||||
|
||||
pub async fn [<$name:snake _async>](
|
||||
channel: SessionChannel,
|
||||
params: [<$name:camel Params>],
|
||||
) -> LuaResult<$ret> {
|
||||
try_timeout!(params.to_timeout_duration(), async move {
|
||||
let f = |
|
||||
mut $channel: SessionChannel,
|
||||
$tenant: &'static str,
|
||||
$params: [<$name:camel Params>]
|
||||
| async move $block;
|
||||
f(channel, TENANT.as_str(), params).await
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
make_api!(append_file, (), { path: PathBuf, data: Vec<u8> }, |channel, tenant, params| {
|
||||
channel.append_file(tenant, params.path, params.data).await
|
||||
});
|
||||
|
||||
make_api!(append_file_text, (), { path: PathBuf, text: String }, |channel, tenant, params| {
|
||||
channel.append_file_text(tenant, params.path, params.text).await
|
||||
});
|
||||
|
||||
make_api!(copy, (), { src: PathBuf, dst: PathBuf }, |channel, tenant, params| {
|
||||
channel.copy(tenant, params.src, params.dst).await
|
||||
});
|
||||
|
||||
make_api!(create_dir, (), { path: PathBuf, #[serde(default)] all: bool }, |channel, tenant, params| {
|
||||
channel.create_dir(tenant, params.path, params.all).await
|
||||
});
|
||||
|
||||
make_api!(
|
||||
exists,
|
||||
bool,
|
||||
{ path: PathBuf },
|
||||
|channel, tenant, params| { channel.exists(tenant, params.path).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
metadata,
|
||||
Metadata,
|
||||
{
|
||||
path: PathBuf,
|
||||
#[serde(default)] canonicalize: bool,
|
||||
#[serde(default)] resolve_file_type: bool
|
||||
},
|
||||
|channel, tenant, params| {
|
||||
channel.metadata(
|
||||
tenant,
|
||||
params.path,
|
||||
params.canonicalize,
|
||||
params.resolve_file_type
|
||||
).await
|
||||
}
|
||||
);
|
||||
|
||||
make_api!(
|
||||
read_dir,
|
||||
(Vec<DirEntry>, Vec<Failure>),
|
||||
{
|
||||
path: PathBuf,
|
||||
#[serde(default = "default_depth")] depth: usize,
|
||||
#[serde(default)] absolute: bool,
|
||||
#[serde(default)] canonicalize: bool,
|
||||
#[serde(default)] include_root: bool
|
||||
},
|
||||
|channel, tenant, params| {
|
||||
channel.read_dir(
|
||||
tenant,
|
||||
params.path,
|
||||
params.depth,
|
||||
params.absolute,
|
||||
params.canonicalize,
|
||||
params.include_root,
|
||||
).await
|
||||
}
|
||||
);
|
||||
|
||||
make_api!(
|
||||
read_file,
|
||||
Vec<u8>,
|
||||
{ path: PathBuf },
|
||||
|channel, tenant, params| { channel.read_file(tenant, params.path).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
read_file_text,
|
||||
String,
|
||||
{ path: PathBuf },
|
||||
|channel, tenant, params| { channel.read_file_text(tenant, params.path).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
remove,
|
||||
(),
|
||||
{ path: PathBuf, #[serde(default)] force: bool },
|
||||
|channel, tenant, params| { channel.remove(tenant, params.path, params.force).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
rename,
|
||||
(),
|
||||
{ src: PathBuf, dst: PathBuf },
|
||||
|channel, tenant, params| { channel.rename(tenant, params.src, params.dst).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
spawn,
|
||||
RemoteProcess,
|
||||
{ cmd: String, #[serde(default)] args: Vec<String>, #[serde(default)] pty: Option<PtySize>, #[serde(default)] persist: bool },
|
||||
|channel, tenant, params| {
|
||||
channel.spawn(tenant, params.cmd, params.args, params.persist, params.pty).await
|
||||
}
|
||||
);
|
||||
|
||||
make_api!(
|
||||
spawn_wait,
|
||||
Output,
|
||||
{ cmd: String, #[serde(default)] args: Vec<String>, #[serde(default)] pty: Option<PtySize>, #[serde(default)] persist: bool },
|
||||
|channel, tenant, params| {
|
||||
let proc = channel.spawn(
|
||||
tenant,
|
||||
params.cmd,
|
||||
params.args,
|
||||
params.persist,
|
||||
params.pty,
|
||||
).await.to_lua_err()?;
|
||||
let id = LuaRemoteProcess::from_distant_async(proc).await?.id;
|
||||
LuaRemoteProcess::output_async(id).await
|
||||
}
|
||||
);
|
||||
|
||||
make_api!(
|
||||
spawn_lsp,
|
||||
RemoteLspProcess,
|
||||
{ cmd: String, #[serde(default)] args: Vec<String>, #[serde(default)] pty: Option<PtySize>, #[serde(default)] persist: bool },
|
||||
|channel, tenant, params| {
|
||||
channel.spawn_lsp(tenant, params.cmd, params.args, params.persist, params.pty).await
|
||||
}
|
||||
);
|
||||
|
||||
make_api!(system_info, SystemInfo, |channel, tenant, _params| {
|
||||
channel.system_info(tenant).await
|
||||
});
|
||||
|
||||
make_api!(
|
||||
write_file,
|
||||
(),
|
||||
{ path: PathBuf, data: Vec<u8> },
|
||||
|channel, tenant, params| { channel.write_file(tenant, params.path, params.data).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
write_file_text,
|
||||
(),
|
||||
{ path: PathBuf, text: String },
|
||||
|channel, tenant, params| { channel.write_file_text(tenant, params.path, params.text).await }
|
||||
);
|
@ -1,374 +0,0 @@
|
||||
use crate::constants::TIMEOUT_MILLIS;
|
||||
use distant_ssh2::{Ssh2AuthHandler, Ssh2SessionOpts};
|
||||
use mlua::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use std::{fmt, io, time::Duration};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ConnectOpts {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub key: String,
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for ConnectOpts {
|
||||
fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
match lua_value {
|
||||
LuaValue::Table(tbl) => Ok(Self {
|
||||
host: tbl.get("host")?,
|
||||
port: tbl.get("port")?,
|
||||
key: tbl.get("key")?,
|
||||
timeout: {
|
||||
let milliseconds: Option<u64> = tbl.get("timeout")?;
|
||||
Duration::from_millis(milliseconds.unwrap_or(TIMEOUT_MILLIS))
|
||||
},
|
||||
}),
|
||||
LuaValue::Nil => Err(LuaError::FromLuaConversionError {
|
||||
from: "Nil",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Boolean(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Boolean",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::LightUserData(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "LightUserData",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Integer(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Integer",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Number(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Number",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::String(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "String",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Function(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Function",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Thread(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Thread",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::UserData(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "UserData",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Error(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Error",
|
||||
to: "ConnectOpts",
|
||||
message: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LaunchOpts<'a> {
|
||||
/// Host to connect to remotely (e.g. example.com)
|
||||
pub host: String,
|
||||
|
||||
/// Mode to use for communication (ssh or distant server)
|
||||
pub mode: Mode,
|
||||
|
||||
/// Callbacks to be triggered on various authentication events
|
||||
pub handler: Ssh2AuthHandler<'a>,
|
||||
|
||||
/// Miscellaneous ssh configuration options
|
||||
pub ssh: Ssh2SessionOpts,
|
||||
|
||||
/// Options specific to launching the distant binary on the remote machine
|
||||
pub distant: LaunchDistantOpts,
|
||||
|
||||
/// Maximum time to wait for launch to complete
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
impl fmt::Debug for LaunchOpts<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("LaunchOpts")
|
||||
.field("host", &self.host)
|
||||
.field("mode", &self.mode)
|
||||
.field("handler", &"...")
|
||||
.field("ssh", &self.ssh)
|
||||
.field("distant", &self.distant)
|
||||
.field("timeout", &self.timeout)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for LaunchOpts<'lua> {
|
||||
fn from_lua(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
||||
let Ssh2AuthHandler {
|
||||
on_authenticate,
|
||||
on_banner,
|
||||
on_error,
|
||||
on_host_verify,
|
||||
} = Default::default();
|
||||
|
||||
match lua_value {
|
||||
LuaValue::Table(tbl) => Ok(Self {
|
||||
host: tbl.get("host")?,
|
||||
mode: {
|
||||
let mode: Option<LuaValue> = tbl.get("mode")?;
|
||||
match mode {
|
||||
Some(value) => lua.from_value(value)?,
|
||||
None => Default::default(),
|
||||
}
|
||||
},
|
||||
handler: Ssh2AuthHandler {
|
||||
on_authenticate: {
|
||||
let f: Option<LuaFunction> = tbl.get("on_authenticate")?;
|
||||
match f {
|
||||
Some(f) => Box::new(move |ev| {
|
||||
let value = to_value!(lua, &ev)
|
||||
.map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))?;
|
||||
f.call::<LuaValue, Vec<String>>(value)
|
||||
.map_err(|x| io::Error::new(io::ErrorKind::Other, x))
|
||||
}),
|
||||
None => on_authenticate,
|
||||
}
|
||||
},
|
||||
on_banner: {
|
||||
let f: Option<LuaFunction> = tbl.get("on_banner")?;
|
||||
match f {
|
||||
Some(f) => Box::new(move |banner| {
|
||||
let _ = f.call::<String, ()>(banner.to_string());
|
||||
}),
|
||||
None => on_banner,
|
||||
}
|
||||
},
|
||||
on_host_verify: {
|
||||
let f: Option<LuaFunction> = tbl.get("on_host_verify")?;
|
||||
match f {
|
||||
Some(f) => Box::new(move |host| {
|
||||
f.call::<String, bool>(host.to_string())
|
||||
.map_err(|x| io::Error::new(io::ErrorKind::Other, x))
|
||||
}),
|
||||
None => on_host_verify,
|
||||
}
|
||||
},
|
||||
on_error: {
|
||||
let f: Option<LuaFunction> = tbl.get("on_error")?;
|
||||
match f {
|
||||
Some(f) => Box::new(move |err| {
|
||||
let _ = f.call::<String, ()>(err.to_string());
|
||||
}),
|
||||
None => on_error,
|
||||
}
|
||||
},
|
||||
},
|
||||
ssh: {
|
||||
let ssh_tbl: Option<LuaValue> = tbl.get("ssh")?;
|
||||
match ssh_tbl {
|
||||
Some(value) => lua.from_value(value)?,
|
||||
None => Default::default(),
|
||||
}
|
||||
},
|
||||
distant: {
|
||||
let distant_tbl: Option<LuaValue> = tbl.get("distant")?;
|
||||
match distant_tbl {
|
||||
Some(value) => LaunchDistantOpts::from_lua(value, lua)?,
|
||||
None => Default::default(),
|
||||
}
|
||||
},
|
||||
timeout: {
|
||||
let milliseconds: Option<u64> = tbl.get("timeout")?;
|
||||
Duration::from_millis(milliseconds.unwrap_or(TIMEOUT_MILLIS))
|
||||
},
|
||||
}),
|
||||
LuaValue::Nil => Err(LuaError::FromLuaConversionError {
|
||||
from: "Nil",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Boolean(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Boolean",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::LightUserData(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "LightUserData",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Integer(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Integer",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Number(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Number",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::String(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "String",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Function(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Function",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Thread(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Thread",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::UserData(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "UserData",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Error(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Error",
|
||||
to: "LaunchOpts",
|
||||
message: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LaunchDistantOpts {
|
||||
/// Binary representing the distant server on the remote machine
|
||||
pub bin: String,
|
||||
|
||||
/// Additional CLI options to pass to the distant server when starting
|
||||
pub args: String,
|
||||
|
||||
/// If true, will run distant via `echo <CMD> | $SHELL -l`, which will spawn a login shell to
|
||||
/// execute distant
|
||||
pub use_login_shell: bool,
|
||||
}
|
||||
|
||||
impl Default for LaunchDistantOpts {
|
||||
/// Create default options
|
||||
///
|
||||
/// * bin = "distant"
|
||||
/// * args = ""
|
||||
/// * use_login_shell = false
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bin: String::from("distant"),
|
||||
args: String::new(),
|
||||
use_login_shell: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for LaunchDistantOpts {
|
||||
fn from_lua(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
||||
let LaunchDistantOpts {
|
||||
bin: default_bin,
|
||||
args: default_args,
|
||||
use_login_shell: default_use_login_shell,
|
||||
} = Default::default();
|
||||
|
||||
match lua_value {
|
||||
LuaValue::Table(tbl) => Ok(Self {
|
||||
bin: {
|
||||
let bin: Option<String> = tbl.get("bin")?;
|
||||
bin.unwrap_or(default_bin)
|
||||
},
|
||||
|
||||
// Allows "--some --args" or {"--some", "--args"}
|
||||
args: {
|
||||
let value: LuaValue = tbl.get("args")?;
|
||||
match value {
|
||||
LuaValue::Nil => default_args,
|
||||
LuaValue::String(args) => args.to_str()?.to_string(),
|
||||
x => {
|
||||
let args: Vec<String> = lua.from_value(x)?;
|
||||
args.join(" ")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
use_login_shell: tbl
|
||||
.get::<_, Option<bool>>("use_login_shell")?
|
||||
.unwrap_or(default_use_login_shell),
|
||||
}),
|
||||
LuaValue::Nil => Err(LuaError::FromLuaConversionError {
|
||||
from: "Nil",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Boolean(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Boolean",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::LightUserData(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "LightUserData",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Integer(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Integer",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Number(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Number",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::String(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "String",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Function(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Function",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Thread(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Thread",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::UserData(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "UserData",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
LuaValue::Error(_) => Err(LuaError::FromLuaConversionError {
|
||||
from: "Error",
|
||||
to: "LaunchDistantOpts",
|
||||
message: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Mode {
|
||||
Distant,
|
||||
Ssh,
|
||||
}
|
||||
|
||||
impl Default for Mode {
|
||||
fn default() -> Self {
|
||||
Self::Distant
|
||||
}
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
use crate::{constants::PROC_POLL_TIMEOUT, runtime};
|
||||
use distant_core::{
|
||||
data::PtySize, RemoteLspProcess as DistantRemoteLspProcess,
|
||||
RemoteProcess as DistantRemoteProcess,
|
||||
};
|
||||
use mlua::{prelude::*, UserData, UserDataFields, UserDataMethods};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashMap, io, time::Duration};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// Contains mapping of id -> remote process for use in maintaining active processes
|
||||
static PROC_MAP: Lazy<RwLock<HashMap<usize, DistantRemoteProcess>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
/// Contains mapping of id -> remote lsp process for use in maintaining active processes
|
||||
static LSP_PROC_MAP: Lazy<RwLock<HashMap<usize, DistantRemoteLspProcess>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
macro_rules! with_proc {
|
||||
($map_name:ident, $id:expr, $proc:ident -> $f:expr) => {{
|
||||
let id = $id;
|
||||
let mut lock = runtime::get_runtime()?.block_on($map_name.write());
|
||||
let $proc = lock.get_mut(&id).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("No remote process found with id {}", id),
|
||||
)
|
||||
.to_lua_err()
|
||||
})?;
|
||||
$f
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! with_proc_async {
|
||||
($map_name:ident, $id:expr, $proc:ident -> $f:expr) => {{
|
||||
let id = $id;
|
||||
let mut lock = $map_name.write().await;
|
||||
let $proc = lock.get_mut(&id).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("No remote process found with id {}", id),
|
||||
)
|
||||
.to_lua_err()
|
||||
})?;
|
||||
$f
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! impl_process {
|
||||
($name:ident, $type:ty, $map_name:ident) => {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct $name {
|
||||
pub(crate) id: usize,
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub fn new(id: usize) -> Self {
|
||||
Self { id }
|
||||
}
|
||||
|
||||
pub fn from_distant(proc: $type) -> LuaResult<Self> {
|
||||
runtime::get_runtime()?.block_on(Self::from_distant_async(proc))
|
||||
}
|
||||
|
||||
pub async fn from_distant_async(proc: $type) -> LuaResult<Self> {
|
||||
let id = proc.id();
|
||||
$map_name.write().await.insert(id, proc);
|
||||
Ok(Self::new(id))
|
||||
}
|
||||
|
||||
fn is_active(id: usize) -> LuaResult<bool> {
|
||||
Ok(runtime::get_runtime()?.block_on($map_name.read()).contains_key(&id))
|
||||
}
|
||||
|
||||
fn write_stdin(id: usize, data: String) -> LuaResult<()> {
|
||||
runtime::block_on(Self::write_stdin_async(id, data))
|
||||
}
|
||||
|
||||
async fn write_stdin_async(id: usize, data: String) -> LuaResult<()> {
|
||||
// NOTE: We must spawn a task that continually tries to send stdin as
|
||||
// if we wait until successful then we hold the lock the entire time
|
||||
runtime::spawn(async move {
|
||||
loop {
|
||||
let is_done = with_proc_async!($map_name, id, proc -> {
|
||||
let res = proc.stdin
|
||||
.as_mut()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, "Stdin closed").to_lua_err()
|
||||
})?
|
||||
.try_write(data.as_bytes());
|
||||
match res {
|
||||
Ok(_) => Ok(true),
|
||||
Err(x) if x.kind() == io::ErrorKind::WouldBlock => Ok(false),
|
||||
Err(x) => Err(x),
|
||||
}
|
||||
})?;
|
||||
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(PROC_POLL_TIMEOUT)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}).await
|
||||
}
|
||||
|
||||
fn close_stdin(id: usize) -> LuaResult<()> {
|
||||
with_proc!($map_name, id, proc -> {
|
||||
let _ = proc.stdin.take();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn read_stdout(id: usize) -> LuaResult<Option<Vec<u8>>> {
|
||||
with_proc!($map_name, id, proc -> {
|
||||
proc.stdout
|
||||
.as_mut()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, "Stdout closed").to_lua_err()
|
||||
})?
|
||||
.try_read()
|
||||
.to_lua_err()
|
||||
})
|
||||
}
|
||||
|
||||
async fn read_stdout_async(id: usize) -> LuaResult<Vec<u8>> {
|
||||
// NOTE: We must spawn a task that continually tries to read stdout as
|
||||
// if we wait until successful then we hold the lock the entire time
|
||||
runtime::spawn(async move {
|
||||
loop {
|
||||
let data = with_proc_async!($map_name, id, proc -> {
|
||||
proc.stdout
|
||||
.as_mut()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, "Stdout closed").to_lua_err()
|
||||
})?
|
||||
.try_read()
|
||||
.to_lua_err()?
|
||||
});
|
||||
|
||||
if let Some(data) = data {
|
||||
break Ok(data);
|
||||
}
|
||||
}
|
||||
}).await
|
||||
}
|
||||
|
||||
fn read_stderr(id: usize) -> LuaResult<Option<Vec<u8>>> {
|
||||
with_proc!($map_name, id, proc -> {
|
||||
proc.stderr
|
||||
.as_mut()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, "Stderr closed").to_lua_err()
|
||||
})?
|
||||
.try_read()
|
||||
.to_lua_err()
|
||||
})
|
||||
}
|
||||
|
||||
async fn read_stderr_async(id: usize) -> LuaResult<Vec<u8>> {
|
||||
// NOTE: We must spawn a task that continually tries to read stdout as
|
||||
// if we wait until successful then we hold the lock the entire time
|
||||
runtime::spawn(async move {
|
||||
loop {
|
||||
let data = with_proc_async!($map_name, id, proc -> {
|
||||
proc.stderr
|
||||
.as_mut()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, "Stderr closed").to_lua_err()
|
||||
})?
|
||||
.try_read()
|
||||
.to_lua_err()?
|
||||
});
|
||||
|
||||
if let Some(data) = data {
|
||||
break Ok(data);
|
||||
}
|
||||
}
|
||||
}).await
|
||||
}
|
||||
|
||||
fn resize(id: usize, size: PtySize) -> LuaResult<()> {
|
||||
runtime::block_on(Self::resize_async(id, size))
|
||||
}
|
||||
|
||||
async fn resize_async(id: usize, size: PtySize) -> LuaResult<()> {
|
||||
with_proc_async!($map_name, id, proc -> {
|
||||
proc.resize(size).await.to_lua_err()
|
||||
})
|
||||
}
|
||||
|
||||
fn kill(id: usize) -> LuaResult<()> {
|
||||
runtime::block_on(Self::kill_async(id))
|
||||
}
|
||||
|
||||
async fn kill_async(id: usize) -> LuaResult<()> {
|
||||
with_proc_async!($map_name, id, proc -> {
|
||||
proc.kill().await.to_lua_err()
|
||||
})
|
||||
}
|
||||
|
||||
fn status(id: usize) -> LuaResult<Option<Status>> {
|
||||
runtime::block_on(Self::status_async(id))
|
||||
}
|
||||
|
||||
async fn status_async(id: usize) -> LuaResult<Option<Status>> {
|
||||
let lock = $map_name.read().await;
|
||||
let proc = lock.get(&id).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("No remote process found with id {}", id),
|
||||
)
|
||||
.to_lua_err()
|
||||
})?;
|
||||
Ok(proc.status().await.map(|(success, exit_code)| Status {
|
||||
success,
|
||||
exit_code,
|
||||
}))
|
||||
}
|
||||
|
||||
fn wait(id: usize) -> LuaResult<(bool, Option<i32>)> {
|
||||
runtime::block_on(Self::wait_async(id))
|
||||
}
|
||||
|
||||
async fn wait_async(id: usize) -> LuaResult<(bool, Option<i32>)> {
|
||||
let proc = $map_name.write().await.remove(&id).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("No remote process found with id {}", id),
|
||||
)
|
||||
.to_lua_err()
|
||||
})?;
|
||||
|
||||
proc.wait().await.to_lua_err()
|
||||
}
|
||||
|
||||
fn output(id: usize) -> LuaResult<Output> {
|
||||
runtime::block_on(Self::output_async(id))
|
||||
}
|
||||
|
||||
pub(crate) async fn output_async(id: usize) -> LuaResult<Output> {
|
||||
let mut proc = $map_name.write().await.remove(&id).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("No remote process found with id {}", id),
|
||||
)
|
||||
.to_lua_err()
|
||||
})?;
|
||||
|
||||
// Remove the stdout and stderr streams before letting process run to completion
|
||||
let mut stdout = proc.stdout.take().unwrap();
|
||||
let mut stderr = proc.stderr.take().unwrap();
|
||||
|
||||
// Gather stdout and stderr after process completes
|
||||
let (success, exit_code) = proc.wait().await.to_lua_err()?;
|
||||
|
||||
let mut stdout_buf = Vec::new();
|
||||
while let Ok(Some(data)) = stdout.try_read() {
|
||||
stdout_buf.extend(data);
|
||||
}
|
||||
|
||||
let mut stderr_buf = Vec::new();
|
||||
while let Ok(Some(data)) = stderr.try_read() {
|
||||
stderr_buf.extend(data);
|
||||
}
|
||||
|
||||
Ok(Output {
|
||||
success,
|
||||
exit_code,
|
||||
stdout: stdout_buf,
|
||||
stderr: stderr_buf,
|
||||
})
|
||||
}
|
||||
|
||||
fn abort(id: usize) -> LuaResult<()> {
|
||||
with_proc!($map_name, id, proc -> {
|
||||
Ok(proc.abort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for $name {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, this| Ok(this.id));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("is_active", |_, this, ()| Self::is_active(this.id));
|
||||
methods.add_method("close_stdin", |_, this, ()| Self::close_stdin(this.id));
|
||||
methods.add_method("write_stdin", |_, this, data: String| {
|
||||
Self::write_stdin(this.id, data)
|
||||
});
|
||||
methods.add_async_method("write_stdin_async", |_, this, data: String| {
|
||||
runtime::spawn(Self::write_stdin_async(this.id, data))
|
||||
});
|
||||
methods.add_method("read_stdout", |_, this, ()| Self::read_stdout(this.id));
|
||||
methods.add_async_method("read_stdout_async", |_, this, ()| {
|
||||
runtime::spawn(Self::read_stdout_async(this.id))
|
||||
});
|
||||
methods.add_method("read_stderr", |_, this, ()| Self::read_stderr(this.id));
|
||||
methods.add_async_method("read_stderr_async", |_, this, ()| {
|
||||
runtime::spawn(Self::read_stderr_async(this.id))
|
||||
});
|
||||
methods.add_method("status", |_, this, ()| Self::status(this.id));
|
||||
methods.add_async_method("status_async", |_, this, ()| {
|
||||
runtime::spawn(Self::status_async(this.id))
|
||||
});
|
||||
methods.add_method("wait", |_, this, ()| Self::wait(this.id));
|
||||
methods.add_async_method("wait_async", |_, this, ()| {
|
||||
runtime::spawn(Self::wait_async(this.id))
|
||||
});
|
||||
methods.add_method("output", |_, this, ()| Self::output(this.id));
|
||||
methods.add_async_method("output_async", |_, this, ()| {
|
||||
runtime::spawn(Self::output_async(this.id))
|
||||
});
|
||||
methods.add_method("resize", |lua, this, value: LuaValue| {
|
||||
let size: PtySize = lua.from_value(value)?;
|
||||
Self::resize(this.id, size)
|
||||
});
|
||||
methods.add_async_method("resize_async", |lua, this, value: LuaValue| {
|
||||
let size: LuaResult<PtySize> = lua.from_value(value);
|
||||
runtime::spawn(async move {
|
||||
let size = size?;
|
||||
Self::resize_async(this.id, size).await
|
||||
})
|
||||
});
|
||||
methods.add_method("kill", |_, this, ()| Self::kill(this.id));
|
||||
methods.add_async_method("kill_async", |_, this, ()| {
|
||||
runtime::spawn(Self::kill_async(this.id))
|
||||
});
|
||||
methods.add_method("abort", |_, this, ()| Self::abort(this.id));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Represents process status
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Status {
|
||||
pub success: bool,
|
||||
pub exit_code: Option<i32>,
|
||||
}
|
||||
|
||||
impl UserData for Status {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("success", |_, this| Ok(this.success));
|
||||
fields.add_field_method_get("exit_code", |_, this| Ok(this.exit_code));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("to_tbl", |lua, this, ()| {
|
||||
let tbl = lua.create_table()?;
|
||||
tbl.set("success", this.success)?;
|
||||
tbl.set("exit_code", this.exit_code)?;
|
||||
Ok(tbl)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents process output
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Output {
|
||||
pub success: bool,
|
||||
pub exit_code: Option<i32>,
|
||||
pub stdout: Vec<u8>,
|
||||
pub stderr: Vec<u8>,
|
||||
}
|
||||
|
||||
impl UserData for Output {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("success", |_, this| Ok(this.success));
|
||||
fields.add_field_method_get("exit_code", |_, this| Ok(this.exit_code));
|
||||
fields.add_field_method_get("stdout", |_, this| Ok(this.stdout.to_vec()));
|
||||
fields.add_field_method_get("stderr", |_, this| Ok(this.stderr.to_vec()));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("to_tbl", |lua, this, ()| {
|
||||
let tbl = lua.create_table()?;
|
||||
tbl.set("success", this.success)?;
|
||||
tbl.set("exit_code", this.exit_code)?;
|
||||
tbl.set("stdout", this.stdout.to_vec())?;
|
||||
tbl.set("stderr", this.stdout.to_vec())?;
|
||||
Ok(tbl)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl_process!(RemoteProcess, DistantRemoteProcess, PROC_MAP);
|
||||
impl_process!(RemoteLspProcess, DistantRemoteLspProcess, LSP_PROC_MAP);
|
@ -1,126 +0,0 @@
|
||||
use crate::constants::NVIM_POLL_TIMEOUT;
|
||||
use mlua::{chunk, prelude::*};
|
||||
use once_cell::sync::OnceCell;
|
||||
use oorandom::Rand32;
|
||||
use std::{
|
||||
sync::Mutex,
|
||||
time::{SystemTime, SystemTimeError, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
/// Makes a Lua table containing the utils functions
|
||||
pub fn make_utils_tbl(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let tbl = lua.create_table()?;
|
||||
|
||||
tbl.set(
|
||||
"nvim_wrap_async",
|
||||
lua.create_function(|lua, (async_fn, millis): (LuaFunction, Option<u64>)| {
|
||||
nvim_wrap_async(lua, async_fn, millis.unwrap_or(NVIM_POLL_TIMEOUT))
|
||||
})?,
|
||||
)?;
|
||||
tbl.set(
|
||||
"wrap_async",
|
||||
lua.create_function(|lua, (async_fn, schedule_fn)| wrap_async(lua, async_fn, schedule_fn))?,
|
||||
)?;
|
||||
tbl.set("rand_u32", lua.create_function(|_, ()| rand_u32())?)?;
|
||||
|
||||
Ok(tbl)
|
||||
}
|
||||
|
||||
/// Specialty function that performs wrap_async using `vim.defer_fn` from neovim
|
||||
pub fn nvim_wrap_async<'a>(
|
||||
lua: &'a Lua,
|
||||
async_fn: LuaFunction<'a>,
|
||||
millis: u64,
|
||||
) -> LuaResult<LuaFunction<'a>> {
|
||||
let schedule_fn = lua
|
||||
.load(chunk! {
|
||||
function(cb)
|
||||
return vim.defer_fn(cb, $millis)
|
||||
end
|
||||
})
|
||||
.eval()?;
|
||||
wrap_async(lua, async_fn, schedule_fn)
|
||||
}
|
||||
|
||||
/// Wraps an async function and a scheduler function such that
|
||||
/// a new function is returned that takes a callback when the async
|
||||
/// function completes as well as zero or more arguments to provide
|
||||
/// to the async function when first executing it
|
||||
///
|
||||
/// ```lua
|
||||
/// local f = wrap_async(some_async_fn, schedule_fn)
|
||||
/// f(arg1, arg2, ..., function(success, res) end)
|
||||
/// ```
|
||||
pub fn wrap_async<'a>(
|
||||
lua: &'a Lua,
|
||||
async_fn: LuaFunction<'a>,
|
||||
schedule_fn: LuaFunction<'a>,
|
||||
) -> LuaResult<LuaFunction<'a>> {
|
||||
let pending = pending(lua)?;
|
||||
lua.load(chunk! {
|
||||
return function(...)
|
||||
local args = {...}
|
||||
local cb = table.remove(args)
|
||||
|
||||
assert(type(cb) == "function", "Invalid type for cb")
|
||||
local schedule = function(...) return $schedule_fn(...) end
|
||||
|
||||
// Wrap the async function in a coroutine so we can poll it
|
||||
local thread = coroutine.create(function(...) return $async_fn(...) end)
|
||||
|
||||
// Start the future by peforming the first poll
|
||||
local status, res = coroutine.resume(thread, unpack(args))
|
||||
|
||||
local inner_fn
|
||||
inner_fn = function()
|
||||
// Thread has exited already, so res is an error
|
||||
if not status then
|
||||
cb(false, res)
|
||||
// Got pending status on success, so we are still waiting
|
||||
elseif res == $pending then
|
||||
// Resume the coroutine and then schedule a followup
|
||||
// once it has completed another round
|
||||
status, res = coroutine.resume(thread)
|
||||
schedule(inner_fn)
|
||||
// Got success with non-pending status, so this should be the result
|
||||
else
|
||||
cb(true, res)
|
||||
end
|
||||
end
|
||||
schedule(inner_fn)
|
||||
end
|
||||
})
|
||||
.eval()
|
||||
}
|
||||
|
||||
/// Return mlua's internal `Poll::Pending`
|
||||
pub(super) fn pending(lua: &Lua) -> LuaResult<LuaValue> {
|
||||
let pending = lua.create_async_function(|_, ()| async move {
|
||||
tokio::task::yield_now().await;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Should return mlua's internal Poll::Pending that is statically available
|
||||
// See https://github.com/khvzak/mlua/issues/76#issuecomment-932645078
|
||||
lua.load(chunk! {
|
||||
(coroutine.wrap($pending))()
|
||||
})
|
||||
.eval()
|
||||
}
|
||||
|
||||
/// Return a random u32
|
||||
pub fn rand_u32() -> LuaResult<u32> {
|
||||
static RAND: OnceCell<Mutex<Rand32>> = OnceCell::new();
|
||||
|
||||
Ok(RAND
|
||||
.get_or_try_init::<_, SystemTimeError>(|| {
|
||||
Ok(Mutex::new(Rand32::new(
|
||||
SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(),
|
||||
)))
|
||||
})
|
||||
.to_lua_err()?
|
||||
.lock()
|
||||
.map_err(|x| x.to_string())
|
||||
.to_lua_err()?
|
||||
.rand_u32())
|
||||
}
|
@ -0,0 +1,526 @@
|
||||
use crate::cli::{fixtures::*, utils::random_tenant};
|
||||
use assert_fs::prelude::*;
|
||||
use distant_core::{data::ErrorKind, Request, RequestData, Response, ResponseData};
|
||||
use rstest::*;
|
||||
use std::{
|
||||
io,
|
||||
io::{BufRead, BufReader, Read, Write},
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn wait_a_bit() {
|
||||
wait_millis(250);
|
||||
}
|
||||
|
||||
fn wait_even_longer() {
|
||||
wait_millis(500);
|
||||
}
|
||||
|
||||
fn wait_millis(millis: u64) {
|
||||
thread::sleep(Duration::from_millis(millis));
|
||||
}
|
||||
|
||||
struct ThreadedReader {
|
||||
#[allow(dead_code)]
|
||||
handle: thread::JoinHandle<io::Result<()>>,
|
||||
rx: mpsc::Receiver<String>,
|
||||
}
|
||||
|
||||
impl ThreadedReader {
|
||||
pub fn new<R>(reader: R) -> Self
|
||||
where
|
||||
R: Read + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let handle = thread::spawn(move || {
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(0) => break Ok(()),
|
||||
Ok(_) => {
|
||||
// Consume the line and create an empty line to
|
||||
// be filled in next time
|
||||
let line2 = line;
|
||||
line = String::new();
|
||||
|
||||
if let Err(line) = tx.send(line2) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Failed to pass along line because channel closed! Line: '{}'",
|
||||
line.0
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(x) => return Err(x),
|
||||
}
|
||||
}
|
||||
});
|
||||
Self { handle, rx }
|
||||
}
|
||||
|
||||
/// Tries to read the next line if available
|
||||
pub fn try_read_line(&mut self) -> Option<String> {
|
||||
self.rx.try_recv().ok()
|
||||
}
|
||||
|
||||
/// Reads the next line, waiting for at minimum "timeout"
|
||||
pub fn try_read_line_timeout(&mut self, timeout: Duration) -> Option<String> {
|
||||
let start_time = Instant::now();
|
||||
let mut checked_at_least_once = false;
|
||||
|
||||
while !checked_at_least_once || start_time.elapsed() < timeout {
|
||||
if let Some(line) = self.try_read_line() {
|
||||
return Some(line);
|
||||
}
|
||||
|
||||
checked_at_least_once = true;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Reads the next line, waiting for at minimum "timeout" before panicking
|
||||
pub fn read_line_timeout(&mut self, timeout: Duration) -> String {
|
||||
let start_time = Instant::now();
|
||||
let mut checked_at_least_once = false;
|
||||
|
||||
while !checked_at_least_once || start_time.elapsed() < timeout {
|
||||
if let Some(line) = self.try_read_line() {
|
||||
return line;
|
||||
}
|
||||
|
||||
checked_at_least_once = true;
|
||||
}
|
||||
|
||||
panic!("Reached timeout of {:?}", timeout);
|
||||
}
|
||||
|
||||
/// Reads the next line, waiting for at minimum default timeout before panicking
|
||||
#[allow(dead_code)]
|
||||
pub fn read_line_default_timeout(&mut self) -> String {
|
||||
self.read_line_timeout(Self::default_timeout())
|
||||
}
|
||||
|
||||
/// Tries to read the next response if available
|
||||
///
|
||||
/// Will panic if next line is not a valid response
|
||||
#[allow(dead_code)]
|
||||
pub fn try_read_response(&mut self) -> Option<Response> {
|
||||
self.try_read_line().map(|line| {
|
||||
serde_json::from_str(&line)
|
||||
.unwrap_or_else(|_| panic!("Invalid response format for {}", line))
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads the next response, waiting for at minimum "timeout" before panicking
|
||||
pub fn read_response_timeout(&mut self, timeout: Duration) -> Response {
|
||||
let line = self.read_line_timeout(timeout);
|
||||
serde_json::from_str(&line)
|
||||
.unwrap_or_else(|_| panic!("Invalid response format for {}", line))
|
||||
}
|
||||
|
||||
/// Reads the next response, waiting for at minimum default timeout before panicking
|
||||
pub fn read_response_default_timeout(&mut self) -> Response {
|
||||
self.read_response_timeout(Self::default_timeout())
|
||||
}
|
||||
|
||||
/// Creates a new duration representing a default timeout for the threaded reader
|
||||
pub fn default_timeout() -> Duration {
|
||||
Duration::from_millis(250)
|
||||
}
|
||||
|
||||
/// Waits for reader to shut down, returning the result
|
||||
#[allow(dead_code)]
|
||||
pub fn wait(self) -> io::Result<()> {
|
||||
match self.handle.join() {
|
||||
Ok(x) => x,
|
||||
Err(x) => std::panic::resume_unwind(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_watch_request<W>(
|
||||
writer: &mut W,
|
||||
reader: &mut ThreadedReader,
|
||||
path: impl Into<PathBuf>,
|
||||
recursive: bool,
|
||||
) -> Response
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let req = Request {
|
||||
id: rand::random(),
|
||||
tenant: random_tenant(),
|
||||
payload: vec![RequestData::Watch {
|
||||
path: path.into(),
|
||||
recursive,
|
||||
only: Vec::new(),
|
||||
except: Vec::new(),
|
||||
}],
|
||||
};
|
||||
|
||||
// Send our request to the process
|
||||
let msg = format!("{}\n", serde_json::to_string(&req).unwrap());
|
||||
writer
|
||||
.write_all(msg.as_bytes())
|
||||
.expect("Failed to write to process");
|
||||
|
||||
// Pause a bit to ensure that the process started and processed our request
|
||||
wait_a_bit();
|
||||
|
||||
// Ensure we got an acknowledgement of watching
|
||||
let res = reader.read_response_default_timeout();
|
||||
assert_eq!(res.payload[0], ResponseData::Ok);
|
||||
res
|
||||
}
|
||||
|
||||
// TODO: For some reason, this always fails on linux, so we're skipping the test
|
||||
// for that platform right now.
|
||||
#[rstest]
|
||||
#[cfg_attr(linux, ignore)]
|
||||
fn should_support_watching_a_single_file(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("file");
|
||||
file.touch().unwrap();
|
||||
|
||||
// distant action watch {path}
|
||||
let mut child = action_std_cmd
|
||||
.args(&["watch", file.to_str().unwrap()])
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
|
||||
// Wait for the process to be ready
|
||||
wait_a_bit();
|
||||
|
||||
// Manipulate the file
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
// Pause a bit to ensure that the change is detected and reported
|
||||
wait_even_longer();
|
||||
|
||||
let mut stdout = ThreadedReader::new(child.stdout.take().unwrap());
|
||||
let mut stdout_data = String::new();
|
||||
while let Some(line) = stdout.try_read_line_timeout(ThreadedReader::default_timeout()) {
|
||||
stdout_data.push_str(&line);
|
||||
}
|
||||
|
||||
// Close out the process and collect the output
|
||||
let _ = child.kill().expect("Failed to terminate process");
|
||||
let output = child.wait_with_output().expect("Failed to wait for output");
|
||||
let stderr_data = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
|
||||
let path = file
|
||||
.to_path_buf()
|
||||
.canonicalize()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
// Verify we get information printed out about the change
|
||||
assert!(
|
||||
stdout_data.contains(&path),
|
||||
"\"{}\" missing {}",
|
||||
stdout_data,
|
||||
path
|
||||
);
|
||||
assert_eq!(stderr_data, "");
|
||||
}
|
||||
|
||||
// TODO: For some reason, this always fails on linux, so we're skipping the test
|
||||
// for that platform right now.
|
||||
#[rstest]
|
||||
#[cfg_attr(linux, ignore)]
|
||||
fn should_support_watching_a_directory_recursively(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
|
||||
let file = dir.child("file");
|
||||
file.touch().unwrap();
|
||||
|
||||
// distant action watch {path}
|
||||
let mut child = action_std_cmd
|
||||
.args(&["watch", "--recursive", temp.to_str().unwrap()])
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
|
||||
// Wait for the process to be ready
|
||||
wait_a_bit();
|
||||
|
||||
// Manipulate the file
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
// Pause a bit to ensure that the change is detected and reported
|
||||
wait_even_longer();
|
||||
|
||||
let mut stdout = ThreadedReader::new(child.stdout.take().unwrap());
|
||||
let mut stdout_data = String::new();
|
||||
while let Some(line) = stdout.try_read_line_timeout(ThreadedReader::default_timeout()) {
|
||||
stdout_data.push_str(&line);
|
||||
}
|
||||
|
||||
// Close out the process and collect the output
|
||||
let _ = child.kill().expect("Failed to terminate process");
|
||||
let output = child.wait_with_output().expect("Failed to wait for output");
|
||||
let stderr_data = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
|
||||
let path = file
|
||||
.to_path_buf()
|
||||
.canonicalize()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
// Verify we get information printed out about the change
|
||||
assert!(
|
||||
stdout_data.contains(&path),
|
||||
"\"{}\" missing {}",
|
||||
stdout_data,
|
||||
path
|
||||
);
|
||||
assert_eq!(stderr_data, "");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn yield_an_error_when_fails(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let invalid_path = temp.to_path_buf().join("missing");
|
||||
|
||||
// distant action watch {path}
|
||||
let child = action_std_cmd
|
||||
.args(&["watch", invalid_path.to_str().unwrap()])
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
|
||||
// Pause a bit to ensure that the process started and failed
|
||||
wait_a_bit();
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.expect("Failed to wait for child to complete");
|
||||
|
||||
// Verify we get information printed out about the change
|
||||
assert!(!output.status.success(), "Child unexpectedly succeeded");
|
||||
assert!(output.stdout.is_empty(), "Unexpectedly got stdout");
|
||||
assert!(!output.stderr.is_empty(), "Missing stderr output");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_json_watching_single_file(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let file = temp.child("file");
|
||||
file.touch().unwrap();
|
||||
|
||||
// distant action --format json --interactive
|
||||
let mut cmd = action_std_cmd
|
||||
.args(&["--format", "json"])
|
||||
.arg("--interactive")
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
let mut stdin = cmd.stdin.take().unwrap();
|
||||
let mut stdout = ThreadedReader::new(cmd.stdout.take().unwrap());
|
||||
|
||||
let _ = send_watch_request(&mut stdin, &mut stdout, file.to_path_buf(), false);
|
||||
|
||||
// Make a change to some file
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
// Pause a bit to ensure that the process detected the change and reported it
|
||||
wait_even_longer();
|
||||
|
||||
// Get the response and verify the change
|
||||
// NOTE: Don't bother checking the kind as it can vary by platform
|
||||
let res = stdout.read_response_default_timeout();
|
||||
match &res.payload[0] {
|
||||
ResponseData::Changed(change) => {
|
||||
assert_eq!(&change.paths, &[file.to_path_buf().canonicalize().unwrap()]);
|
||||
}
|
||||
x => panic!("Unexpected response: {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_json_watching_directory_recursively(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let dir = temp.child("dir");
|
||||
dir.create_dir_all().unwrap();
|
||||
|
||||
let file = dir.child("file");
|
||||
file.touch().unwrap();
|
||||
|
||||
// distant action --format json --interactive
|
||||
let mut cmd = action_std_cmd
|
||||
.args(&["--format", "json"])
|
||||
.arg("--interactive")
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
let mut stdin = cmd.stdin.take().unwrap();
|
||||
let mut stdout = ThreadedReader::new(cmd.stdout.take().unwrap());
|
||||
|
||||
let _ = send_watch_request(&mut stdin, &mut stdout, temp.to_path_buf(), true);
|
||||
|
||||
// Make a change to some file
|
||||
file.write_str("some text").unwrap();
|
||||
|
||||
// Pause a bit to ensure that the process detected the change and reported it
|
||||
wait_even_longer();
|
||||
|
||||
// Get the response and verify the change
|
||||
// NOTE: Don't bother checking the kind as it can vary by platform
|
||||
let res = stdout.read_response_default_timeout();
|
||||
match &res.payload[0] {
|
||||
ResponseData::Changed(change) => {
|
||||
assert_eq!(&change.paths, &[file.to_path_buf().canonicalize().unwrap()]);
|
||||
}
|
||||
x => panic!("Unexpected response: {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_json_reporting_changes_using_correct_request_id(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let file1 = temp.child("file1");
|
||||
file1.touch().unwrap();
|
||||
|
||||
let file2 = temp.child("file2");
|
||||
file2.touch().unwrap();
|
||||
|
||||
// distant action --format json --interactive
|
||||
let mut cmd = action_std_cmd
|
||||
.args(&["--format", "json"])
|
||||
.arg("--interactive")
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
let mut stdin = cmd.stdin.take().unwrap();
|
||||
let mut stdout = ThreadedReader::new(cmd.stdout.take().unwrap());
|
||||
|
||||
// Create a request to watch file1
|
||||
let file1_res = send_watch_request(&mut stdin, &mut stdout, file1.to_path_buf(), true);
|
||||
|
||||
// Create a request to watch file2
|
||||
let file2_res = send_watch_request(&mut stdin, &mut stdout, file2.to_path_buf(), true);
|
||||
|
||||
assert_ne!(
|
||||
file1_res.origin_id, file2_res.origin_id,
|
||||
"Two separate watch responses have same origin id"
|
||||
);
|
||||
|
||||
// Make a change to file1
|
||||
file1.write_str("some text").unwrap();
|
||||
|
||||
// Pause a bit to ensure that the process detected the change and reported it
|
||||
wait_even_longer();
|
||||
|
||||
// Get the response and verify the change
|
||||
// NOTE: Don't bother checking the kind as it can vary by platform
|
||||
let file1_change_res = stdout.read_response_default_timeout();
|
||||
match &file1_change_res.payload[0] {
|
||||
ResponseData::Changed(change) => {
|
||||
assert_eq!(
|
||||
&change.paths,
|
||||
&[file1.to_path_buf().canonicalize().unwrap()]
|
||||
);
|
||||
}
|
||||
x => panic!("Unexpected response: {:?}", x),
|
||||
}
|
||||
|
||||
// Process any extra messages (we might get create, content, and more)
|
||||
loop {
|
||||
// Sleep a bit to give time to get all changes happening
|
||||
wait_a_bit();
|
||||
|
||||
if stdout.try_read_line().is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make a change to file2
|
||||
file2.write_str("some text").unwrap();
|
||||
|
||||
// Pause a bit to ensure that the process detected the change and reported it
|
||||
wait_even_longer();
|
||||
|
||||
// Get the response and verify the change
|
||||
// NOTE: Don't bother checking the kind as it can vary by platform
|
||||
let file2_change_res = stdout.read_response_default_timeout();
|
||||
match &file2_change_res.payload[0] {
|
||||
ResponseData::Changed(change) => {
|
||||
assert_eq!(
|
||||
&change.paths,
|
||||
&[file2.to_path_buf().canonicalize().unwrap()]
|
||||
);
|
||||
}
|
||||
x => panic!("Unexpected response: {:?}", x),
|
||||
}
|
||||
|
||||
// Verify that the response origin ids match and are separate
|
||||
assert_eq!(
|
||||
file1_res.origin_id, file1_change_res.origin_id,
|
||||
"File 1 watch origin and change origin are different"
|
||||
);
|
||||
assert_eq!(
|
||||
file2_res.origin_id, file2_change_res.origin_id,
|
||||
"File 1 watch origin and change origin are different"
|
||||
);
|
||||
assert_ne!(
|
||||
file1_change_res.origin_id, file2_change_res.origin_id,
|
||||
"Two separate watch change responses have same origin id"
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn should_support_json_output_for_error(mut action_std_cmd: Command) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let path = temp.to_path_buf().join("missing");
|
||||
|
||||
// distant action --format json --interactive
|
||||
let mut cmd = action_std_cmd
|
||||
.args(&["--format", "json"])
|
||||
.arg("--interactive")
|
||||
.spawn()
|
||||
.expect("Failed to execute");
|
||||
let mut stdin = cmd.stdin.take().unwrap();
|
||||
let mut stdout = ThreadedReader::new(cmd.stdout.take().unwrap());
|
||||
|
||||
let req = Request {
|
||||
id: rand::random(),
|
||||
tenant: random_tenant(),
|
||||
payload: vec![RequestData::Watch {
|
||||
path,
|
||||
recursive: false,
|
||||
only: Vec::new(),
|
||||
except: Vec::new(),
|
||||
}],
|
||||
};
|
||||
|
||||
// Send our request to the process
|
||||
let msg = format!("{}\n", serde_json::to_string(&req).unwrap());
|
||||
stdin
|
||||
.write_all(msg.as_bytes())
|
||||
.expect("Failed to write to process");
|
||||
|
||||
// Pause a bit to ensure that the process started and processed our request
|
||||
wait_even_longer();
|
||||
|
||||
// Ensure we got an acknowledgement of watching
|
||||
let res = stdout.read_response_default_timeout();
|
||||
match &res.payload[0] {
|
||||
ResponseData::Error(x) => {
|
||||
assert_eq!(x.kind, ErrorKind::NotFound);
|
||||
}
|
||||
x => panic!("Unexpected response: {:?}", x),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue