mirror of https://github.com/chipsenkbeil/distant
Add lua lib & support compiling distant cli on windows (#59)
* Update distant-ssh2 with new changes to wezterm-ssh * Implement lua module (distant-lua) * Implement tests for lua module (distant-lua-tests) * Add untested windows daemon support * distant binary now compiles on windows * Split up Github actions for Windows, MacOS, and Linux into individual yaml filespull/96/head
parent
b27f0a4109
commit
16bed4690b
@ -0,0 +1,55 @@
|
||||
name: CI (All)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
name: Lint with clippy
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust (clippy)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check Cargo availability
|
||||
run: cargo --version
|
||||
- name: distant-core (all features)
|
||||
run: cargo clippy -p distant-core --all-targets --verbose --all-features
|
||||
- name: distant-ssh2 (all features)
|
||||
run: cargo clippy -p distant-ssh2 --all-targets --verbose --all-features
|
||||
- name: distant-lua (lua51 & vendored)
|
||||
run: (cd distant-lua && cargo clippy --all-targets --verbose --no-default-features --features "lua51,vendored")
|
||||
shell: bash
|
||||
- name: distant-lua-tests (lua51 & vendored)
|
||||
run: (cd distant-lua-tests && cargo clippy --tests --verbose --no-default-features --features "lua51,vendored")
|
||||
shell: bash
|
||||
- name: distant (all features)
|
||||
run: cargo clippy --all-targets --verbose --all-features
|
||||
|
||||
rustfmt:
|
||||
name: Verify code formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust (rustfmt)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check Cargo availability
|
||||
run: cargo --version
|
||||
- run: cargo fmt --all -- --check
|
@ -0,0 +1,68 @@
|
||||
name: CI (MacOS)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: "Test Rust ${{ matrix.rust }} on ${{ matrix.os }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { rust: stable, os: macos-latest }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check Cargo availability
|
||||
run: cargo --version
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
base: ${{ github.ref }}
|
||||
filters: |
|
||||
cli:
|
||||
- 'src/**'
|
||||
- 'Cargo.*'
|
||||
core:
|
||||
- 'distant-core/**'
|
||||
ssh2:
|
||||
- 'distant-ssh2/**'
|
||||
lua:
|
||||
- 'distant-lua/**'
|
||||
- 'distant-lua-tests/**'
|
||||
- name: Run core tests (default features)
|
||||
run: cargo test --verbose -p distant-core
|
||||
if: steps.changes.outputs.core == 'true'
|
||||
- name: Run core tests (all features)
|
||||
run: cargo test --verbose --all-features -p distant-core
|
||||
if: steps.changes.outputs.core == 'true'
|
||||
- name: Run ssh2 tests (default features)
|
||||
run: cargo test --verbose -p distant-ssh2
|
||||
if: steps.changes.outputs.ssh2 == 'true'
|
||||
- name: Run ssh2 tests (all features)
|
||||
run: cargo test --verbose --all-features -p distant-ssh2
|
||||
if: steps.changes.outputs.ssh2 == 'true'
|
||||
- name: Run CLI tests
|
||||
run: cargo test --verbose
|
||||
shell: bash
|
||||
if: steps.changes.outputs.cli == 'true'
|
||||
- name: Run CLI tests (no default features)
|
||||
run: cargo test --verbose --no-default-features
|
||||
shell: bash
|
||||
if: steps.changes.outputs.cli == 'true'
|
||||
- name: Run Lua tests
|
||||
run: (cd distant-lua && cargo build) && (cd distant-lua-tests && cargo test --verbose)
|
||||
shell: bash
|
||||
if: steps.changes.outputs.lua == 'true'
|
@ -0,0 +1,80 @@
|
||||
name: CI (Windows)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: "Test Rust ${{ matrix.rust }} on ${{ matrix.os }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { rust: stable, os: windows-latest, target: x86_64-pc-windows-msvc }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check Cargo availability
|
||||
run: cargo --version
|
||||
- uses: Vampire/setup-wsl@v1
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
base: ${{ github.ref }}
|
||||
filters: |
|
||||
cli:
|
||||
- 'src/**'
|
||||
- 'Cargo.*'
|
||||
core:
|
||||
- 'distant-core/**'
|
||||
ssh2:
|
||||
- 'distant-ssh2/**'
|
||||
lua:
|
||||
- 'distant-lua/**'
|
||||
- 'distant-lua-tests/**'
|
||||
- name: Run distant-core tests (default features)
|
||||
run: cargo test --verbose -p distant-core
|
||||
if: steps.changes.outputs.core == 'true'
|
||||
- name: Run distant-core tests (all features)
|
||||
run: cargo test --verbose --all-features -p distant-core
|
||||
if: steps.changes.outputs.core == 'true'
|
||||
- name: Build distant-ssh2 (default features)
|
||||
run: cargo build --verbose -p distant-ssh2
|
||||
if: steps.changes.outputs.ssh2 == 'true'
|
||||
- name: Build distant-ssh2 (all features)
|
||||
run: cargo build --verbose --all-features -p distant-ssh2
|
||||
if: steps.changes.outputs.ssh2 == 'true'
|
||||
- name: Build CLI
|
||||
run: cargo build --verbose
|
||||
shell: bash
|
||||
if: steps.changes.outputs.cli == 'true'
|
||||
- name: Build CLI (no default features)
|
||||
run: cargo build --verbose --no-default-features
|
||||
shell: bash
|
||||
if: steps.changes.outputs.cli == 'true'
|
||||
- uses: xpol/setup-lua@v0.3
|
||||
with:
|
||||
lua-version: "5.1.5"
|
||||
if: steps.changes.outputs.lua == 'true'
|
||||
- name: Build Lua (Lua 5.1)
|
||||
run: |
|
||||
cd ${{ github.workspace }}\distant-lua
|
||||
cargo build --verbose --no-default-features --features lua51
|
||||
shell: cmd
|
||||
env:
|
||||
LUA_INC: ${{ github.workspace }}\.lua\include
|
||||
LUA_LIB: ${{ github.workspace }}\.lua\lib
|
||||
LUA_LIB_NAME: lua
|
||||
if: steps.changes.outputs.lua == 'true'
|
@ -0,0 +1,8 @@
|
||||
[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"]
|
@ -0,0 +1,27 @@
|
||||
[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.6.5", 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"] }
|
@ -0,0 +1,34 @@
|
||||
# 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
|
@ -0,0 +1,74 @@
|
||||
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)
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
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)
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
pub mod fixtures;
|
||||
pub mod lua;
|
||||
pub mod poll;
|
||||
pub mod session;
|
@ -0,0 +1,17 @@
|
||||
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()
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
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()
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
mod common;
|
||||
mod lua;
|
@ -0,0 +1,79 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
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, data = $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_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 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, data = $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");
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
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()));
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
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 write_file;
|
||||
mod write_file_text;
|
@ -0,0 +1,357 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,437 @@
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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 stdout")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
})
|
||||
.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")
|
||||
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")
|
||||
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")
|
||||
|
||||
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")
|
||||
assert(stdout == "some text\n", "Unexpected stdout received: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
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, data = $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, data = $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");
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
mod r#async;
|
||||
mod sync;
|
@ -0,0 +1,60 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
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,
|
||||
data = $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_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 text = "some text".to_string();
|
||||
|
||||
let result = lua
|
||||
.load(chunk! {
|
||||
local session = $new_session()
|
||||
session:append_file_text({
|
||||
path = $file_path,
|
||||
data = $text
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("line 1some text");
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
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()));
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
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 write_file;
|
||||
mod write_file_text;
|
@ -0,0 +1,249 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
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());
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,288 @@
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
assert(stdout == "some text\n", "Unexpected stdin sent: " .. stdout)
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
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,
|
||||
data = $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,
|
||||
data = $text
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
assert!(result.is_ok(), "Failed: {}", result.unwrap_err());
|
||||
|
||||
// Also verify that we appended to the file
|
||||
file.assert("some text");
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
[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",
|
||||
]
|
@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "distant-lua"
|
||||
description = "Lua bindings to the distant Rust crates"
|
||||
categories = ["api-bindings", "network-programming"]
|
||||
keywords = ["api", "async"]
|
||||
version = "0.15.0-alpha.6"
|
||||
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.15.0-alpha.6", path = "../distant-core" }
|
||||
distant-ssh2 = { version = "=0.15.0-alpha.6", features = ["serde"], path = "../distant-ssh2" }
|
||||
futures = "0.3.17"
|
||||
log = "0.4.14"
|
||||
mlua = { version = "0.6.5", 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"
|
@ -0,0 +1,65 @@
|
||||
# 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)
|
||||
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.
|
||||
|
||||
```lua
|
||||
local distant = require("distant_lua")
|
||||
|
||||
-- Distant functions are async by design and need to be wrapped in a coroutine
|
||||
-- in order to be used
|
||||
local thread = coroutine.wrap(distant.launch)
|
||||
|
||||
-- Initialize the thread
|
||||
thread({ host = "127.0.0.1" })
|
||||
|
||||
-- Continually check if launch has completed
|
||||
local res
|
||||
while true do
|
||||
res = thread()
|
||||
if res ~= distant.PENDING then
|
||||
break
|
||||
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
|
@ -0,0 +1,31 @@
|
||||
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 log;
|
||||
mod runtime;
|
||||
mod session;
|
||||
mod utils;
|
||||
|
||||
#[mlua::lua_module]
|
||||
fn distant_lua(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let exports = lua.create_table()?;
|
||||
|
||||
exports.set("PENDING", utils::pending(lua)?)?;
|
||||
exports.set("log", log::make_log_tbl(lua)?)?;
|
||||
exports.set("session", session::make_session_tbl(lua)?)?;
|
||||
exports.set("utils", utils::make_utils_tbl(lua)?)?;
|
||||
|
||||
Ok(exports)
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
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)
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
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(),
|
||||
})
|
||||
})
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
use crate::{runtime, utils};
|
||||
use distant_core::{
|
||||
SecretKey32, Session as DistantSession, SessionChannel, XChaCha20Poly1305Codec,
|
||||
};
|
||||
use distant_ssh2::{IntoDistantSessionOpts, Ssh2Session};
|
||||
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_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 get_session_channel(id: usize) -> LuaResult<SessionChannel> {
|
||||
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(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 }
|
||||
}
|
||||
|
||||
/// Launches a new distant session on a remote machine
|
||||
pub async fn launch(opts: LaunchOpts<'_>) -> LuaResult<Self> {
|
||||
let LaunchOpts {
|
||||
host,
|
||||
mode,
|
||||
handler,
|
||||
ssh,
|
||||
timeout,
|
||||
} = opts;
|
||||
|
||||
// First, establish a connection to an SSH server
|
||||
let mut ssh_session = Ssh2Session::connect(host, ssh).to_lua_err()?;
|
||||
|
||||
// Second, authenticate with the server
|
||||
ssh_session.authenticate(handler).await.to_lua_err()?;
|
||||
|
||||
// Third, convert our ssh session into a distant session based on desired method
|
||||
let session = match mode {
|
||||
Mode::Distant => ssh_session
|
||||
.into_distant_session(IntoDistantSessionOpts {
|
||||
timeout,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.to_lua_err()?,
|
||||
Mode::Ssh => ssh_session.into_ssh_client_session().to_lua_err()?,
|
||||
};
|
||||
|
||||
// Fourth, store our current session in our global map and then return a reference
|
||||
let id = utils::rand_u32()? as usize;
|
||||
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> {
|
||||
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()?;
|
||||
|
||||
let key: SecretKey32 = opts.key.parse().to_lua_err()?;
|
||||
let codec = XChaCha20Poly1305Codec::from(key);
|
||||
|
||||
let session = DistantSession::tcp_connect_timeout(addr, codec, opts.timeout)
|
||||
.await
|
||||
.to_lua_err()?;
|
||||
|
||||
let id = utils::rand_u32()? as usize;
|
||||
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: LuaValue| {
|
||||
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: LuaValue| async move {
|
||||
let rt = crate::runtime::get_runtime()?;
|
||||
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));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("is_active", |_, this, _: LuaValue| {
|
||||
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_lsp, |_lua, proc| {
|
||||
Ok(RemoteLspProcess::from_distant(proc))
|
||||
});
|
||||
impl_methods!(methods, write_file);
|
||||
impl_methods!(methods, write_file_text);
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
use crate::runtime;
|
||||
use distant_core::{
|
||||
DirEntry, Error as Failure, Metadata, RemoteLspProcess, RemoteProcess, SessionChannel,
|
||||
SessionChannelExt,
|
||||
};
|
||||
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, data: String }, |channel, tenant, params| {
|
||||
channel.append_file_text(tenant, params.path, params.data).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, args: Vec<String> },
|
||||
|channel, tenant, params| { channel.spawn(tenant, params.cmd, params.args).await }
|
||||
);
|
||||
|
||||
make_api!(
|
||||
spawn_lsp,
|
||||
RemoteLspProcess,
|
||||
{ cmd: String, args: Vec<String> },
|
||||
|channel, tenant, params| { channel.spawn_lsp(tenant, params.cmd, params.args).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, data: String },
|
||||
|channel, tenant, params| { channel.write_file_text(tenant, params.path, params.data).await }
|
||||
);
|
@ -0,0 +1,208 @@
|
||||
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: u64 = tbl.get("timeout")?;
|
||||
Duration::from_millis(milliseconds)
|
||||
},
|
||||
}),
|
||||
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> {
|
||||
pub host: String,
|
||||
pub mode: Mode,
|
||||
pub handler: Ssh2AuthHandler<'a>,
|
||||
pub ssh: Ssh2SessionOpts,
|
||||
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("timeout", &self.timeout)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for LaunchOpts<'lua> {
|
||||
fn from_lua(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
||||
match lua_value {
|
||||
LuaValue::Table(tbl) => Ok(Self {
|
||||
host: tbl.get("host")?,
|
||||
mode: lua.from_value(tbl.get("mode")?)?,
|
||||
handler: Ssh2AuthHandler {
|
||||
on_authenticate: {
|
||||
let f: LuaFunction = tbl.get("on_authenticate")?;
|
||||
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))
|
||||
})
|
||||
},
|
||||
on_banner: {
|
||||
let f: LuaFunction = tbl.get("on_banner")?;
|
||||
Box::new(move |banner| {
|
||||
let _ = f.call::<String, ()>(banner.to_string());
|
||||
})
|
||||
},
|
||||
on_host_verify: {
|
||||
let f: LuaFunction = tbl.get("on_host_verify")?;
|
||||
Box::new(move |host| {
|
||||
f.call::<String, bool>(host.to_string())
|
||||
.map_err(|x| io::Error::new(io::ErrorKind::Other, x))
|
||||
})
|
||||
},
|
||||
on_error: {
|
||||
let f: LuaFunction = tbl.get("on_error")?;
|
||||
Box::new(move |err| {
|
||||
let _ = f.call::<String, ()>(err.to_string());
|
||||
})
|
||||
},
|
||||
},
|
||||
ssh: lua.from_value(tbl.get("ssh")?)?,
|
||||
timeout: {
|
||||
let milliseconds: u64 = tbl.get("timeout")?;
|
||||
Duration::from_millis(milliseconds)
|
||||
},
|
||||
}),
|
||||
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(Copy, Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Mode {
|
||||
Distant,
|
||||
Ssh,
|
||||
}
|
||||
|
||||
impl Default for Mode {
|
||||
fn default() -> Self {
|
||||
Self::Distant
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
use crate::runtime;
|
||||
use distant_core::{
|
||||
RemoteLspProcess as DistantRemoteLspProcess, RemoteProcess as DistantRemoteProcess,
|
||||
};
|
||||
use mlua::{prelude::*, UserData, UserDataFields, UserDataMethods};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashMap, io};
|
||||
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 {
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub fn new(id: usize) -> Self {
|
||||
Self { id }
|
||||
}
|
||||
|
||||
pub fn from_distant(proc: $type) -> LuaResult<Self> {
|
||||
let id = proc.id();
|
||||
runtime::get_runtime()?.block_on($map_name.write()).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<()> {
|
||||
with_proc_async!($map_name, id, proc -> {
|
||||
proc.stdin
|
||||
.as_mut()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::BrokenPipe, "Stdin closed").to_lua_err()
|
||||
})?
|
||||
.write(data.as_str())
|
||||
.await
|
||||
.to_lua_err()
|
||||
})
|
||||
}
|
||||
|
||||
fn close_stdin(id: usize) -> LuaResult<()> {
|
||||
with_proc!($map_name, id, proc -> {
|
||||
let _ = proc.stdin.take();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn read_stdout(id: usize) -> LuaResult<Option<String>> {
|
||||
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<String> {
|
||||
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()
|
||||
})?
|
||||
.read()
|
||||
.await
|
||||
.to_lua_err()
|
||||
})
|
||||
}
|
||||
|
||||
fn read_stderr(id: usize) -> LuaResult<Option<String>> {
|
||||
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<String> {
|
||||
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()
|
||||
})?
|
||||
.read()
|
||||
.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 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("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));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_process!(RemoteProcess, DistantRemoteProcess, PROC_MAP);
|
||||
impl_process!(RemoteLspProcess, DistantRemoteLspProcess, LSP_PROC_MAP);
|
@ -0,0 +1,113 @@
|
||||
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| nvim_wrap_async(lua, async_fn))?,
|
||||
)?;
|
||||
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.schedule` from neovim
|
||||
pub fn nvim_wrap_async<'a>(lua: &'a Lua, async_fn: LuaFunction<'a>) -> LuaResult<LuaFunction<'a>> {
|
||||
let schedule_fn = lua.load("vim.schedule").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())
|
||||
}
|
Loading…
Reference in New Issue