mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 07:26:43 -04:00
Avoid relying on global CLI in tests (#294)
This switches tests from invoking a spacetimedb CLI found on PATH to always using up-to-date version by depending on the CLI crate as a library. --------- Signed-off-by: Phoebe Goldman <phoebe@goldman-tribe.org> Co-authored-by: Phoebe Goldman <phoebe@clockworklabs.io>
This commit is contained in:
@@ -30,10 +30,6 @@ jobs:
|
||||
|
||||
- uses: dsherret/rust-toolchain-file@v1
|
||||
|
||||
- name: Install CLI tool
|
||||
run: |
|
||||
cargo install --path crates/cli
|
||||
|
||||
- name: Create /stdb dir
|
||||
run: |
|
||||
sudo mkdir /stdb
|
||||
|
||||
Generated
+1
@@ -4447,6 +4447,7 @@ name = "spacetimedb-testing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.3.10",
|
||||
"duct",
|
||||
"lazy_static",
|
||||
"rand 0.8.5",
|
||||
|
||||
@@ -850,6 +850,13 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_localhost() -> Self {
|
||||
Self {
|
||||
home: RawConfig::new_with_localhost(),
|
||||
proj: RawConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
let config_path = if let Some(config_path) = std::env::var_os("SPACETIME_CONFIG_FILE") {
|
||||
PathBuf::from(&config_path)
|
||||
|
||||
@@ -11,6 +11,7 @@ spacetimedb-standalone = { path = "../standalone", version = "0.6.1" }
|
||||
spacetimedb-client-api = { path = "../client-api", version = "0.6.1" }
|
||||
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
wasmbin.workspace = true
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use clap::Command;
|
||||
use spacetimedb::config::{FilesLocal, SpacetimeDbFiles};
|
||||
use spacetimedb_cli::Config;
|
||||
use std::env;
|
||||
|
||||
pub mod modules;
|
||||
@@ -17,3 +19,21 @@ pub fn set_key_env_vars(paths: &FilesLocal) {
|
||||
set_if_not_exist("SPACETIMEDB_JWT_PUB_KEY", paths.public_key());
|
||||
set_if_not_exist("SPACETIMEDB_JWT_PRIV_KEY", paths.private_key());
|
||||
}
|
||||
|
||||
pub fn invoke_cli(args: &[&str]) {
|
||||
lazy_static::lazy_static! {
|
||||
static ref RUNTIME: tokio::runtime::Runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
static ref COMMAND: Command = Command::new("spacetime").no_binary_name(true).subcommands(spacetimedb_cli::get_subcommands());
|
||||
static ref CONFIG: Config = Config::new_with_localhost();
|
||||
}
|
||||
|
||||
let args = COMMAND.clone().get_matches_from(args);
|
||||
let (cmd, args) = args.subcommand().expect("Could not split subcommand and args");
|
||||
|
||||
RUNTIME
|
||||
.block_on(spacetimedb_cli::exec_subcommand((*CONFIG).clone(), cmd, args))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
+29
-121
@@ -1,108 +1,27 @@
|
||||
use duct::{cmd, Handle};
|
||||
use duct::cmd;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use std::thread::JoinHandle;
|
||||
use std::{collections::HashSet, fs::create_dir_all, sync::Mutex};
|
||||
|
||||
use crate::invoke_cli;
|
||||
use crate::modules::{module_path, CompiledModule};
|
||||
use std::path::Path;
|
||||
|
||||
struct StandaloneProcess {
|
||||
handle: Handle,
|
||||
num_using: usize,
|
||||
}
|
||||
|
||||
impl StandaloneProcess {
|
||||
fn start() -> Self {
|
||||
let handle = cmd!("spacetime", "start")
|
||||
.stderr_to_stdout()
|
||||
.stdout_capture()
|
||||
.unchecked()
|
||||
.start()
|
||||
.expect("Failed to run `spacetime start`");
|
||||
|
||||
StandaloneProcess { handle, num_using: 1 }
|
||||
pub fn ensure_standalone_process() {
|
||||
lazy_static! {
|
||||
static ref JOIN_HANDLE: Mutex<Option<JoinHandle<()>>> =
|
||||
Mutex::new(Some(std::thread::spawn(|| invoke_cli(&["start"]))));
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> anyhow::Result<()> {
|
||||
assert!(self.num_using == 0);
|
||||
let mut join_handle = JOIN_HANDLE.lock().unwrap();
|
||||
|
||||
self.handle.kill()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn running_or_err(&self) -> anyhow::Result<()> {
|
||||
if let Some(output) = self
|
||||
.handle
|
||||
.try_wait()
|
||||
.expect("Error from spacetime standalone subprocess")
|
||||
{
|
||||
let code = output.status;
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
Err(anyhow::anyhow!(
|
||||
"spacetime start exited unexpectedly. Exit status: {}. Output:\n{}",
|
||||
code,
|
||||
output,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn add_user(&mut self) -> anyhow::Result<()> {
|
||||
self.running_or_err()?;
|
||||
self.num_using += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if the process was stopped because no one is using it.
|
||||
fn sub_user(&mut self) -> anyhow::Result<bool> {
|
||||
self.num_using -= 1;
|
||||
if self.num_using == 0 {
|
||||
self.stop()?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static STANDALONE_PROCESS: Mutex<Option<StandaloneProcess>> = Mutex::new(None);
|
||||
|
||||
/// An RAII handle on the `STANDALONE_PROCESS`.
|
||||
///
|
||||
/// On construction, ensures that the `STANDALONE_PROCESS` is running.
|
||||
///
|
||||
/// On drop, checks to see if it was the last `StandaloneHandle`, and if so,
|
||||
/// terminates the `STANDALONE_PROCESS`.
|
||||
pub struct StandaloneHandle {
|
||||
_hidden: (),
|
||||
}
|
||||
|
||||
impl Default for StandaloneHandle {
|
||||
fn default() -> Self {
|
||||
let mut process = STANDALONE_PROCESS.lock().expect("STANDALONE_PROCESS Mutex is poisoned");
|
||||
if let Some(proc) = &mut *process {
|
||||
proc.add_user()
|
||||
.expect("Failed to add user for running spacetime standalone process");
|
||||
} else {
|
||||
*process = Some(StandaloneProcess::start());
|
||||
}
|
||||
StandaloneHandle { _hidden: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StandaloneHandle {
|
||||
fn drop(&mut self) {
|
||||
let mut process = STANDALONE_PROCESS.lock().expect("STANDALONE_PROCESS Mutex is poisoned");
|
||||
if let Some(proc) = &mut *process {
|
||||
if proc
|
||||
.sub_user()
|
||||
.expect("Failed to remove user for running spacetime standalone process")
|
||||
{
|
||||
*process = None;
|
||||
}
|
||||
}
|
||||
if join_handle
|
||||
.as_ref()
|
||||
.expect("Standalone process already finished")
|
||||
.is_finished()
|
||||
{
|
||||
join_handle.take().unwrap().join().expect("Standalone process failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +99,7 @@ impl Test {
|
||||
TestBuilder::default()
|
||||
}
|
||||
pub fn run(&self) {
|
||||
let _handle = StandaloneHandle::default();
|
||||
ensure_standalone_process();
|
||||
|
||||
let compiled = CompiledModule::compile(&self.module_name);
|
||||
|
||||
@@ -189,12 +108,11 @@ impl Test {
|
||||
compiled.path(),
|
||||
&self.client_project,
|
||||
&self.generate_subdir,
|
||||
&self.name,
|
||||
);
|
||||
|
||||
compile_client(&self.compile_command, &self.client_project, &self.name);
|
||||
|
||||
let db_name = publish_module(&self.module_name, &self.name);
|
||||
let db_name = publish_module(&self.module_name);
|
||||
|
||||
run_client(&self.run_command, &self.client_project, &db_name, &self.name);
|
||||
}
|
||||
@@ -216,22 +134,20 @@ fn random_module_name() -> String {
|
||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
|
||||
}
|
||||
|
||||
fn publish_module(module: &str, test_name: &str) -> String {
|
||||
fn publish_module(module: &str) -> String {
|
||||
let name = random_module_name();
|
||||
let output = cmd!("spacetime", "publish", "--skip_clippy", name.clone(),)
|
||||
.stderr_to_stdout()
|
||||
.stdout_capture()
|
||||
.dir(module_path(module))
|
||||
.unchecked()
|
||||
.run()
|
||||
.expect("Error running spacetime publish");
|
||||
|
||||
status_ok_or_panic(output, "spacetime publish", test_name);
|
||||
invoke_cli(&[
|
||||
"publish",
|
||||
"--project-path",
|
||||
module_path(module).to_str().unwrap(),
|
||||
"--skip_clippy",
|
||||
&name,
|
||||
]);
|
||||
|
||||
name
|
||||
}
|
||||
|
||||
fn generate_bindings(language: &str, path: &Path, client_project: &str, generate_subdir: &str, test_name: &str) {
|
||||
fn generate_bindings(language: &str, path: &Path, client_project: &str, generate_subdir: &str) {
|
||||
let generate_dir = format!("{}/{}", client_project, generate_subdir);
|
||||
|
||||
let mut bindings_lock = BINDINGS_GENERATED.lock().expect("BINDINGS_GENERATED Mutex is poisoned");
|
||||
@@ -245,24 +161,16 @@ fn generate_bindings(language: &str, path: &Path, client_project: &str, generate
|
||||
}
|
||||
|
||||
create_dir_all(&generate_dir).expect("Error creating generate subdir");
|
||||
let output = cmd!(
|
||||
"spacetime",
|
||||
invoke_cli(&[
|
||||
"generate",
|
||||
"--skip_clippy",
|
||||
"--lang",
|
||||
language,
|
||||
"--wasm-file",
|
||||
path,
|
||||
path.to_str().unwrap(),
|
||||
"--out-dir",
|
||||
generate_dir
|
||||
)
|
||||
.stderr_to_stdout()
|
||||
.stdout_capture()
|
||||
.unchecked()
|
||||
.run()
|
||||
.expect("Error running spacetime generate");
|
||||
|
||||
status_ok_or_panic(output, "spacetime generate", test_name);
|
||||
&generate_dir,
|
||||
]);
|
||||
}
|
||||
|
||||
fn split_command_string(command: &str) -> (&str, Vec<&str>) {
|
||||
|
||||
Reference in New Issue
Block a user