mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
initial Abscissa bin (#18)
* abscissa init * remove package in cargo.toml * cargo fmt + remove more stuff fro toml * bump rust edition
This commit is contained in:
parent
a187d9a357
commit
5f20342736
14 changed files with 490 additions and 103 deletions
33
Cargo.toml
33
Cargo.toml
|
@ -1,19 +1,3 @@
|
||||||
[package]
|
|
||||||
name = "cuprate"
|
|
||||||
version = "0.0.1"
|
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.68.0"
|
|
||||||
description = "An upcoming experimental, modern & secure monero node"
|
|
||||||
readme = "readme.md"
|
|
||||||
license = "AGPL-3.0-only"
|
|
||||||
repository = "https://github.com/SyntheticBird45/cuprate"
|
|
||||||
|
|
||||||
|
|
||||||
# All Contributors on github
|
|
||||||
authors=[
|
|
||||||
"SyntheticBird45",
|
|
||||||
"Boog900"
|
|
||||||
]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
|
@ -36,20 +20,3 @@ tracing-subscriber = "*"
|
||||||
|
|
||||||
# As suggested by /u/danda :
|
# As suggested by /u/danda :
|
||||||
thiserror = "*"
|
thiserror = "*"
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
opt-level = 3
|
|
||||||
debug = 0
|
|
||||||
strip = "symbols"
|
|
||||||
lto = "thin"
|
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[build]
|
|
||||||
linker="clang"
|
|
||||||
rustflags=[
|
|
||||||
"-Clink-arg=-fuse-ld=mold",
|
|
||||||
"-Zcf-protection=full",
|
|
||||||
"-Zsanitizer=cfi",
|
|
||||||
"-Crelocation-model=pie",
|
|
||||||
"-Cstack-protector=all",
|
|
||||||
]
|
|
|
@ -1,12 +1,21 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cuprate-bin"
|
name = "cuprate"
|
||||||
|
authors = []
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "*", features = [] }
|
clap = "4"
|
||||||
clap_complete = "*"
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
tracing = { workspace = true }
|
thiserror = "1"
|
||||||
tracing-subscriber = { workspace = true }
|
|
||||||
|
[dependencies.abscissa_core]
|
||||||
|
version = "0.7.0"
|
||||||
|
# optional: use `gimli` to capture backtraces
|
||||||
|
# see https://github.com/rust-lang/backtrace-rs/issues/189
|
||||||
|
# features = ["gimli-backtrace"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
abscissa_core = { version = "0.7.0", features = ["testing"] }
|
||||||
|
once_cell = "1.2"
|
||||||
|
|
||||||
|
|
9
cuprate/README.md
Normal file
9
cuprate/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
This application is authored using [Abscissa], a Rust application framework.
|
||||||
|
|
||||||
|
For more information, see:
|
||||||
|
|
||||||
|
[Documentation]
|
||||||
|
|
||||||
|
[Abscissa]: https://github.com/iqlusioninc/abscissa
|
||||||
|
[Documentation]: https://docs.rs/abscissa_core/
|
88
cuprate/src/application.rs
Normal file
88
cuprate/src/application.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
//! Cuprate Abscissa Application
|
||||||
|
|
||||||
|
use crate::{commands::EntryPoint, config::CuprateConfig};
|
||||||
|
use abscissa_core::{
|
||||||
|
application::{self, AppCell},
|
||||||
|
config::{self, CfgCell},
|
||||||
|
trace, Application, FrameworkError, StandardPaths,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Application state
|
||||||
|
pub static APP: AppCell<CuprateApp> = AppCell::new();
|
||||||
|
|
||||||
|
/// Cuprate Application
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CuprateApp {
|
||||||
|
/// Application configuration.
|
||||||
|
config: CfgCell<CuprateConfig>,
|
||||||
|
|
||||||
|
/// Application state.
|
||||||
|
state: application::State<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a new application instance.
|
||||||
|
///
|
||||||
|
/// By default no configuration is loaded, and the framework state is
|
||||||
|
/// initialized to a default, empty state (no components, threads, etc).
|
||||||
|
impl Default for CuprateApp {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: CfgCell::default(),
|
||||||
|
state: application::State::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for CuprateApp {
|
||||||
|
/// Entrypoint command for this application.
|
||||||
|
type Cmd = EntryPoint;
|
||||||
|
|
||||||
|
/// Application configuration.
|
||||||
|
type Cfg = CuprateConfig;
|
||||||
|
|
||||||
|
/// Paths to resources within the application.
|
||||||
|
type Paths = StandardPaths;
|
||||||
|
|
||||||
|
/// Accessor for application configuration.
|
||||||
|
fn config(&self) -> config::Reader<CuprateConfig> {
|
||||||
|
self.config.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow the application state immutably.
|
||||||
|
fn state(&self) -> &application::State<Self> {
|
||||||
|
&self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register all components used by this application.
|
||||||
|
///
|
||||||
|
/// If you would like to add additional components to your application
|
||||||
|
/// beyond the default ones provided by the framework, this is the place
|
||||||
|
/// to do so.
|
||||||
|
fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> {
|
||||||
|
let framework_components = self.framework_components(command)?;
|
||||||
|
let mut app_components = self.state.components_mut();
|
||||||
|
app_components.register(framework_components)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post-configuration lifecycle callback.
|
||||||
|
///
|
||||||
|
/// Called regardless of whether config is loaded to indicate this is the
|
||||||
|
/// time in app lifecycle when configuration would be loaded if
|
||||||
|
/// possible.
|
||||||
|
fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> {
|
||||||
|
// Configure components
|
||||||
|
let mut components = self.state.components_mut();
|
||||||
|
components.after_config(&config)?;
|
||||||
|
self.config.set_once(config);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get tracing configuration from command-line options
|
||||||
|
fn tracing_config(&self, command: &EntryPoint) -> trace::Config {
|
||||||
|
if command.verbose {
|
||||||
|
trace::Config::verbose()
|
||||||
|
} else {
|
||||||
|
trace::Config::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
cuprate/src/bin/cuprate/main.rs
Normal file
11
cuprate/src/bin/cuprate/main.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//! Main entry point for Cuprate
|
||||||
|
|
||||||
|
#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use cuprate::application::APP;
|
||||||
|
|
||||||
|
/// Boot Cuprate
|
||||||
|
fn main() {
|
||||||
|
abscissa_core::boot(&APP);
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
use crate::CUPRATE_VERSION;
|
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command};
|
|
||||||
use tracing::{event, span, Level, Span};
|
|
||||||
|
|
||||||
/// This function simply contains clap arguments
|
|
||||||
pub fn args() -> ArgMatches {
|
|
||||||
Command::new("Cuprate")
|
|
||||||
.version(CUPRATE_VERSION)
|
|
||||||
.author("Cuprate's contributors")
|
|
||||||
.about("An upcoming experimental, modern, and secure monero node")
|
|
||||||
// Generic Arguments
|
|
||||||
.arg(
|
|
||||||
Arg::new("log")
|
|
||||||
.long("log-level")
|
|
||||||
.value_name("Level")
|
|
||||||
.help("Set the log level")
|
|
||||||
.value_parser(value_parser!(u8))
|
|
||||||
.default_value("1")
|
|
||||||
.long_help("Set the log level. There is 3 log level: <1~INFO, 2~DEBUG >3~TRACE.")
|
|
||||||
.required(false)
|
|
||||||
.action(ArgAction::Set),
|
|
||||||
)
|
|
||||||
.get_matches()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function initialize the FmtSubscriber used by tracing to display event in the console. It send back a span used during runtime.
|
|
||||||
pub fn init(matches: &ArgMatches) -> Span {
|
|
||||||
// Getting the log level from args
|
|
||||||
let log_level = matches.get_one::<u8>("log").unwrap();
|
|
||||||
let level_filter = match log_level {
|
|
||||||
2 => Level::DEBUG,
|
|
||||||
x if x > &2 => Level::TRACE,
|
|
||||||
_ => Level::INFO,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initializing tracing subscriber and runtime span
|
|
||||||
let subscriber = tracing_subscriber::FmtSubscriber::builder()
|
|
||||||
.with_max_level(level_filter)
|
|
||||||
.with_target(false)
|
|
||||||
.finish();
|
|
||||||
tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber for tracing. We prefer to abort the node since without it you have no output in the console");
|
|
||||||
let runtime_span = span!(Level::INFO, "Runtime");
|
|
||||||
let _guard = runtime_span.enter();
|
|
||||||
|
|
||||||
// Notifying log level
|
|
||||||
event!(Level::INFO, "Log level set to {}", level_filter);
|
|
||||||
|
|
||||||
drop(_guard);
|
|
||||||
runtime_span
|
|
||||||
}
|
|
87
cuprate/src/commands.rs
Normal file
87
cuprate/src/commands.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
//! Cuprate Subcommands
|
||||||
|
//!
|
||||||
|
//! This is where you specify the subcommands of your application.
|
||||||
|
//!
|
||||||
|
//! The default application comes with two subcommands:
|
||||||
|
//!
|
||||||
|
//! - `start`: launches the application
|
||||||
|
//! - `--version`: print application version
|
||||||
|
//!
|
||||||
|
//! See the `impl Configurable` below for how to specify the path to the
|
||||||
|
//! application's configuration file.
|
||||||
|
|
||||||
|
mod start;
|
||||||
|
|
||||||
|
use self::start::StartCmd;
|
||||||
|
use crate::config::CuprateConfig;
|
||||||
|
use abscissa_core::{config::Override, Command, Configurable, FrameworkError, Runnable};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Cuprate Configuration Filename
|
||||||
|
pub const CONFIG_FILE: &str = "cuprate.toml";
|
||||||
|
|
||||||
|
/// Cuprate Subcommands
|
||||||
|
/// Subcommands need to be listed in an enum.
|
||||||
|
#[derive(clap::Parser, Command, Debug, Runnable)]
|
||||||
|
pub enum CuprateCmd {
|
||||||
|
/// The `start` subcommand
|
||||||
|
Start(StartCmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry point for the application. It needs to be a struct to allow using subcommands!
|
||||||
|
#[derive(clap::Parser, Command, Debug)]
|
||||||
|
#[command(author, about, version)]
|
||||||
|
pub struct EntryPoint {
|
||||||
|
#[command(subcommand)]
|
||||||
|
cmd: CuprateCmd,
|
||||||
|
|
||||||
|
/// Enable verbose logging
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub verbose: bool,
|
||||||
|
|
||||||
|
/// Use the specified config file
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for EntryPoint {
|
||||||
|
fn run(&self) {
|
||||||
|
self.cmd.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait allows you to define how application configuration is loaded.
|
||||||
|
impl Configurable<CuprateConfig> for EntryPoint {
|
||||||
|
/// Location of the configuration file
|
||||||
|
fn config_path(&self) -> Option<PathBuf> {
|
||||||
|
// Check if the config file exists, and if it does not, ignore it.
|
||||||
|
// If you'd like for a missing configuration file to be a hard error
|
||||||
|
// instead, always return `Some(CONFIG_FILE)` here.
|
||||||
|
let filename = self
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| CONFIG_FILE.into());
|
||||||
|
|
||||||
|
if filename.exists() {
|
||||||
|
Some(filename)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply changes to the config after it's been loaded, e.g. overriding
|
||||||
|
/// values in a config file using command-line options.
|
||||||
|
///
|
||||||
|
/// This can be safely deleted if you don't want to override config
|
||||||
|
/// settings from command-line options.
|
||||||
|
fn process_config(&self, config: CuprateConfig) -> Result<CuprateConfig, FrameworkError> {
|
||||||
|
match &self.cmd {
|
||||||
|
CuprateCmd::Start(cmd) => cmd.override_config(config),
|
||||||
|
//
|
||||||
|
// If you don't need special overrides for some
|
||||||
|
// subcommands, you can just use a catch all
|
||||||
|
// _ => Ok(config),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
cuprate/src/commands/start.rs
Normal file
42
cuprate/src/commands/start.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//! `start` subcommand - example of how to write a subcommand
|
||||||
|
|
||||||
|
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
|
||||||
|
/// accessors along with logging macros. Customize as you see fit.
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::config::CuprateConfig;
|
||||||
|
use abscissa_core::{config, Command, FrameworkError, Runnable};
|
||||||
|
|
||||||
|
/// `start` subcommand
|
||||||
|
///
|
||||||
|
/// The `Parser` proc macro generates an option parser based on the struct
|
||||||
|
/// definition, and is defined in the `clap` crate. See their documentation
|
||||||
|
/// for a more comprehensive example:
|
||||||
|
///
|
||||||
|
/// <https://docs.rs/clap/>
|
||||||
|
#[derive(clap::Parser, Command, Debug)]
|
||||||
|
pub struct StartCmd {
|
||||||
|
/// To whom are we saying hello?
|
||||||
|
recipient: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for StartCmd {
|
||||||
|
/// Start the application.
|
||||||
|
fn run(&self) {
|
||||||
|
let config = APP.config();
|
||||||
|
println!("Hello, {}!", &config.hello.recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl config::Override<CuprateConfig> for StartCmd {
|
||||||
|
// Process the given command line options, overriding settings from
|
||||||
|
// a configuration file using explicit flags taken from command-line
|
||||||
|
// arguments.
|
||||||
|
fn override_config(&self, mut config: CuprateConfig) -> Result<CuprateConfig, FrameworkError> {
|
||||||
|
if !self.recipient.is_empty() {
|
||||||
|
config.hello.recipient = self.recipient.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
45
cuprate/src/config.rs
Normal file
45
cuprate/src/config.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//! Cuprate Config
|
||||||
|
//!
|
||||||
|
//! See instructions in `commands.rs` to specify the path to your
|
||||||
|
//! application's configuration file and/or command-line options
|
||||||
|
//! for specifying it.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Cuprate Configuration
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct CuprateConfig {
|
||||||
|
/// An example configuration section
|
||||||
|
pub hello: ExampleSection,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default configuration settings.
|
||||||
|
///
|
||||||
|
/// Note: if your needs are as simple as below, you can
|
||||||
|
/// use `#[derive(Default)]` on CuprateConfig instead.
|
||||||
|
impl Default for CuprateConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
hello: ExampleSection::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Example configuration section.
|
||||||
|
///
|
||||||
|
/// Delete this and replace it with your actual configuration structs.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct ExampleSection {
|
||||||
|
/// Example configuration value
|
||||||
|
pub recipient: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ExampleSection {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
recipient: "world".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
cuprate/src/error.rs
Normal file
70
cuprate/src/error.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//! Error types
|
||||||
|
|
||||||
|
use abscissa_core::error::{BoxError, Context};
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Display},
|
||||||
|
io,
|
||||||
|
ops::Deref,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Kinds of errors
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
/// Error in configuration file
|
||||||
|
#[error("config error")]
|
||||||
|
Config,
|
||||||
|
|
||||||
|
/// Input/output error
|
||||||
|
#[error("I/O error")]
|
||||||
|
Io,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorKind {
|
||||||
|
/// Create an error context from this error
|
||||||
|
pub fn context(self, source: impl Into<BoxError>) -> Context<ErrorKind> {
|
||||||
|
Context::new(self, Some(source.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(Box<Context<ErrorKind>>);
|
||||||
|
|
||||||
|
impl Deref for Error {
|
||||||
|
type Target = Context<ErrorKind>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Context<ErrorKind> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
self.0.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ErrorKind> for Error {
|
||||||
|
fn from(kind: ErrorKind) -> Self {
|
||||||
|
Context::new(kind, None).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Context<ErrorKind>> for Error {
|
||||||
|
fn from(context: Context<ErrorKind>) -> Self {
|
||||||
|
Error(Box::new(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(err: io::Error) -> Self {
|
||||||
|
ErrorKind::Io.context(err).into()
|
||||||
|
}
|
||||||
|
}
|
22
cuprate/src/lib.rs
Normal file
22
cuprate/src/lib.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
//! Cuprate
|
||||||
|
//!
|
||||||
|
//! Application based on the [Abscissa] framework.
|
||||||
|
//!
|
||||||
|
//! [Abscissa]: https://github.com/iqlusioninc/abscissa
|
||||||
|
|
||||||
|
// Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
trivial_casts,
|
||||||
|
unused_lifetimes,
|
||||||
|
unused_qualifications
|
||||||
|
)]
|
||||||
|
|
||||||
|
pub mod application;
|
||||||
|
pub mod commands;
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
pub mod prelude;
|
|
@ -1,13 +0,0 @@
|
||||||
use tracing::{event, info, span, Level};
|
|
||||||
|
|
||||||
pub mod cli;
|
|
||||||
|
|
||||||
const CUPRATE_VERSION: &str = "0.1.0";
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Collecting options
|
|
||||||
let matches = cli::args();
|
|
||||||
|
|
||||||
// Initializing tracing subscriber and runtime span
|
|
||||||
let _runtime_span = cli::init(&matches);
|
|
||||||
}
|
|
9
cuprate/src/prelude.rs
Normal file
9
cuprate/src/prelude.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//! Application-local prelude: conveniently import types/functions/macros
|
||||||
|
//! which are generally useful and should be available in every module with
|
||||||
|
//! `use crate::prelude::*;
|
||||||
|
|
||||||
|
/// Abscissa core prelude
|
||||||
|
pub use abscissa_core::prelude::*;
|
||||||
|
|
||||||
|
/// Application state
|
||||||
|
pub use crate::application::APP;
|
91
cuprate/tests/acceptance.rs
Normal file
91
cuprate/tests/acceptance.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
//! Acceptance test: runs the application as a subprocess and asserts its
|
||||||
|
//! output for given argument combinations matches what is expected.
|
||||||
|
//!
|
||||||
|
//! Modify and/or delete these as you see fit to test the specific needs of
|
||||||
|
//! your application.
|
||||||
|
//!
|
||||||
|
//! For more information, see:
|
||||||
|
//! <https://docs.rs/abscissa_core/latest/abscissa_core/testing/index.html>
|
||||||
|
|
||||||
|
// Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
trivial_casts,
|
||||||
|
unused_lifetimes,
|
||||||
|
unused_qualifications
|
||||||
|
)]
|
||||||
|
|
||||||
|
use abscissa_core::testing::prelude::*;
|
||||||
|
use cuprate::config::CuprateConfig;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
/// Executes your application binary via `cargo run`.
|
||||||
|
///
|
||||||
|
/// Storing this value as a [`Lazy`] static ensures that all instances of
|
||||||
|
/// the runner acquire a mutex when executing commands and inspecting
|
||||||
|
/// exit statuses, serializing what would otherwise be multithreaded
|
||||||
|
/// invocations as `cargo test` executes tests in parallel by default.
|
||||||
|
pub static RUNNER: Lazy<CmdRunner> = Lazy::new(|| CmdRunner::default());
|
||||||
|
|
||||||
|
/// Use `CuprateConfig::default()` value if no config or args
|
||||||
|
#[test]
|
||||||
|
fn start_no_args() {
|
||||||
|
let mut runner = RUNNER.clone();
|
||||||
|
let mut cmd = runner.arg("start").capture_stdout().run();
|
||||||
|
cmd.stdout().expect_line("Hello, world!");
|
||||||
|
cmd.wait().unwrap().expect_success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use command-line argument value
|
||||||
|
#[test]
|
||||||
|
fn start_with_args() {
|
||||||
|
let mut runner = RUNNER.clone();
|
||||||
|
let mut cmd = runner
|
||||||
|
.args(&["start", "acceptance", "test"])
|
||||||
|
.capture_stdout()
|
||||||
|
.run();
|
||||||
|
|
||||||
|
cmd.stdout().expect_line("Hello, acceptance test!");
|
||||||
|
cmd.wait().unwrap().expect_success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use configured value
|
||||||
|
#[test]
|
||||||
|
fn start_with_config_no_args() {
|
||||||
|
let mut config = CuprateConfig::default();
|
||||||
|
config.hello.recipient = "configured recipient".to_owned();
|
||||||
|
let expected_line = format!("Hello, {}!", &config.hello.recipient);
|
||||||
|
|
||||||
|
let mut runner = RUNNER.clone();
|
||||||
|
let mut cmd = runner.config(&config).arg("start").capture_stdout().run();
|
||||||
|
cmd.stdout().expect_line(&expected_line);
|
||||||
|
cmd.wait().unwrap().expect_success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override configured value with command-line argument
|
||||||
|
#[test]
|
||||||
|
fn start_with_config_and_args() {
|
||||||
|
let mut config = CuprateConfig::default();
|
||||||
|
config.hello.recipient = "configured recipient".to_owned();
|
||||||
|
|
||||||
|
let mut runner = RUNNER.clone();
|
||||||
|
let mut cmd = runner
|
||||||
|
.config(&config)
|
||||||
|
.args(&["start", "acceptance", "test"])
|
||||||
|
.capture_stdout()
|
||||||
|
.run();
|
||||||
|
|
||||||
|
cmd.stdout().expect_line("Hello, acceptance test!");
|
||||||
|
cmd.wait().unwrap().expect_success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Example of a test which matches a regular expression
|
||||||
|
#[test]
|
||||||
|
fn version_no_args() {
|
||||||
|
let mut runner = RUNNER.clone();
|
||||||
|
let mut cmd = runner.arg("--version").capture_stdout().run();
|
||||||
|
cmd.stdout().expect_regex(r"\A\w+ [\d\.\-]+\z");
|
||||||
|
}
|
Loading…
Reference in a new issue