Add --request subcommand for testing (#2498)

This commit is contained in:
Casey Rodarmor 2024-12-01 16:37:08 -08:00 committed by GitHub
parent 7d56d5214e
commit cdf104bf8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 115 additions and 16 deletions

View file

@ -53,6 +53,7 @@ mod cmd {
pub(crate) const INIT: &str = "INIT";
pub(crate) const LIST: &str = "LIST";
pub(crate) const MAN: &str = "MAN";
pub(crate) const REQUEST: &str = "REQUEST";
pub(crate) const SHOW: &str = "SHOW";
pub(crate) const SUMMARY: &str = "SUMMARY";
pub(crate) const VARIABLES: &str = "VARIABLES";
@ -69,6 +70,7 @@ mod cmd {
INIT,
LIST,
MAN,
REQUEST,
SHOW,
SUMMARY,
VARIABLES,
@ -517,6 +519,17 @@ impl Config {
.help("Print man page")
.help_heading(cmd::HEADING),
)
.arg(
Arg::new(cmd::REQUEST)
.long("request")
.action(ArgAction::Set)
.hide(true)
.help(
"Execute <REQUEST>. For internal testing purposes only. May be changed or removed at \
any time.",
)
.help_heading(cmd::REQUEST),
)
.arg(
Arg::new(cmd::SHOW)
.short('s')
@ -696,6 +709,11 @@ impl Config {
}
} else if matches.get_flag(cmd::MAN) {
Subcommand::Man
} else if let Some(request) = matches.get_one::<String>(cmd::REQUEST) {
Subcommand::Request {
request: serde_json::from_str(request)
.map_err(|source| ConfigError::RequestParse { source })?,
}
} else if let Some(path) = matches.get_many::<String>(cmd::SHOW) {
Subcommand::Show {
path: Self::parse_module_path(path)?,

View file

@ -12,6 +12,8 @@ pub(crate) enum ConfigError {
Internal { message: String },
#[snafu(display("Invalid module path `{}`", path.join(" ")))]
ModulePath { path: Vec<String> },
#[snafu(display("Failed to parse request: {source}"))]
RequestParse { source: serde_json::Error },
#[snafu(display(
"Path-prefixed recipes may not be used with `--working-directory` or `--justfile`."
))]

View file

@ -81,7 +81,7 @@ pub(crate) enum Error<'src> {
},
DotenvRequired,
DumpJson {
serde_json_error: serde_json::Error,
source: serde_json::Error,
},
EditorInvoke {
editor: OsString,
@ -359,8 +359,8 @@ impl ColorDisplay for Error<'_> {
DotenvRequired => {
write!(f, "Dotenv file not found")?;
}
DumpJson { serde_json_error } => {
write!(f, "Failed to dump JSON to stdout: {serde_json_error}")?;
DumpJson { source } => {
write!(f, "Failed to dump JSON to stdout: {source}")?;
}
EditorInvoke { editor, io_error } => {
let editor = editor.to_string_lossy();

View file

@ -42,7 +42,7 @@ pub(crate) use {
regex::Regex,
serde::{
ser::{SerializeMap, SerializeSeq},
Serialize, Serializer,
Deserialize, Serialize, Serializer,
},
snafu::{ResultExt, Snafu},
std::{
@ -76,9 +76,12 @@ pub(crate) use crate::{node::Node, tree::Tree};
pub use crate::run::run;
#[doc(hidden)]
use request::Request;
// Used in integration tests.
#[doc(hidden)]
pub use unindent::unindent;
pub use {request::Response, unindent::unindent};
type CompileResult<'a, T = ()> = Result<T, CompileError<'a>>;
type ConfigResult<T> = Result<T, ConfigError>;
@ -106,6 +109,10 @@ pub mod fuzzing;
#[doc(hidden)]
pub mod summary;
// Used for testing with the `--request` subcommand.
#[doc(hidden)]
pub mod request;
mod alias;
mod analyzer;
mod argument_parser;

13
src/request.rs Normal file
View file

@ -0,0 +1,13 @@
use super::*;
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Request {
EnvironmentVariable(String),
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Response {
EnvironmentVariable(Option<OsString>),
}

View file

@ -35,6 +35,9 @@ pub(crate) enum Subcommand {
path: ModulePath,
},
Man,
Request {
request: Request,
},
Run {
arguments: Vec<String>,
overrides: BTreeMap<String, String>,
@ -71,10 +74,6 @@ impl Subcommand {
let justfile = &compilation.justfile;
match self {
Run {
arguments,
overrides,
} => Self::run(config, loader, search, compilation, arguments, overrides)?,
Choose { overrides, chooser } => {
Self::choose(config, justfile, &search, overrides, chooser.as_deref())?;
}
@ -85,6 +84,11 @@ impl Subcommand {
Format => Self::format(config, &search, compilation)?,
Groups => Self::groups(config, justfile),
List { path } => Self::list(config, justfile, path)?,
Request { request } => Self::request(request)?,
Run {
arguments,
overrides,
} => Self::run(config, loader, search, compilation, arguments, overrides)?,
Show { path } => Self::show(config, justfile, path)?,
Summary => Self::summary(config, justfile),
Variables => Self::variables(justfile),
@ -280,7 +284,7 @@ impl Subcommand {
match config.dump_format {
DumpFormat::Json => {
serde_json::to_writer(io::stdout(), &compilation.justfile)
.map_err(|serde_json_error| Error::DumpJson { serde_json_error })?;
.map_err(|source| Error::DumpJson { source })?;
println!();
}
DumpFormat::Just => print!("{}", compilation.root_ast()),
@ -402,6 +406,16 @@ impl Subcommand {
Ok(())
}
fn request(request: &Request) -> RunResult<'static> {
let response = match request {
Request::EnvironmentVariable(key) => Response::EnvironmentVariable(env::var_os(key)),
};
serde_json::to_writer(io::stdout(), &response).map_err(|source| Error::DumpJson { source })?;
Ok(())
}
fn list(config: &Config, mut module: &Justfile, path: &ModulePath) -> RunResult<'static> {
for name in &path.path {
module = module

View file

@ -48,14 +48,13 @@ fn constants_can_be_redefined() {
fn constants_are_not_exported() {
Test::new()
.justfile(
"
r#"
set export
foo:
echo $HEXUPPER
",
@'{{just_executable()}}' --request '{"environment-variable": "HEXUPPER"}'
"#,
)
.stderr_regex(".*HEXUPPER: unbound variable.*")
.status(127)
.response(Response::EnvironmentVariable(None))
.run();
}

View file

@ -6,7 +6,7 @@ pub(crate) use {
test::{assert_eval_eq, Output, Test},
},
executable_path::executable_path,
just::unindent,
just::{unindent, Response},
libc::{EXIT_FAILURE, EXIT_SUCCESS},
pretty_assertions::Comparison,
regex::Regex,
@ -99,6 +99,7 @@ mod quote;
mod readme;
mod recursion_limit;
mod regexes;
mod request;
mod run;
mod script;
mod search;

29
tests/request.rs Normal file
View file

@ -0,0 +1,29 @@
use super::*;
#[test]
fn environment_variable_set() {
Test::new()
.justfile(
r#"
export BAR := 'baz'
@foo:
'{{just_executable()}}' --request '{"environment-variable": "BAR"}'
"#,
)
.response(Response::EnvironmentVariable(Some("baz".into())))
.run();
}
#[test]
fn environment_variable_missing() {
Test::new()
.justfile(
r#"
@foo:
'{{just_executable()}}' --request '{"environment-variable": "FOO_BAR_BAZ"}'
"#,
)
.response(Response::EnvironmentVariable(None))
.run();
}

View file

@ -50,6 +50,7 @@ pub(crate) struct Test {
pub(crate) env: BTreeMap<String, String>,
pub(crate) expected_files: BTreeMap<PathBuf, Vec<u8>>,
pub(crate) justfile: Option<String>,
pub(crate) response: Option<Response>,
pub(crate) shell: bool,
pub(crate) status: i32,
pub(crate) stderr: String,
@ -74,6 +75,7 @@ impl Test {
env: BTreeMap::new(),
expected_files: BTreeMap::new(),
justfile: Some(String::new()),
response: None,
shell: true,
status: EXIT_SUCCESS,
stderr: String::new(),
@ -139,6 +141,11 @@ impl Test {
self
}
pub(crate) fn response(mut self, response: Response) -> Self {
self.response = Some(response);
self.stdout_regex(".*")
}
pub(crate) fn shell(mut self, shell: bool) -> Self {
self.shell = shell;
self
@ -293,6 +300,15 @@ impl Test {
panic!("Output mismatch.");
}
if let Some(ref response) = self.response {
assert_eq!(
&serde_json::from_str::<Response>(output_stdout)
.expect("failed to deserialize stdout as response"),
response,
"response mismatch"
);
}
for (path, expected) in &self.expected_files {
let actual = fs::read(self.tempdir.path().join(path)).unwrap();
assert_eq!(