1
Fork 0

Rollup merge of #82311 - aDotInTheVoid:jsondocck-improvements, r=jyn514

Jsondocck improvements

Adds 2 new commands, ```@is``` and ```@set`.``

```@is``` works like ```@has`,`` except instead of checking if any value matches, it checks that there is exactly one value, and it matches. This allows more precise testing.

```@set``` gets a value, and saves it to be used later. This makes it possible to check that an item appears in the correct module.

Once this lands, the rest of the test suite can be upgraded to use these.

cc ``@CraftSpider``

 ``@rustbot`` modify labels: +T-rustdoc +A-rustdoc-json +A-testsuite
This commit is contained in:
Dylan DPC 2021-02-23 16:10:28 +01:00 committed by GitHub
commit 619e47b886
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 27 deletions

View file

@ -1,24 +1,28 @@
// edition:2018 // edition:2018
// @has nested.json "$.index[*][?(@.name=='nested')].kind" \"module\" // @is nested.json "$.index[*][?(@.name=='nested')].kind" \"module\"
// @has - "$.index[*][?(@.name=='nested')].inner.is_crate" true // @is - "$.index[*][?(@.name=='nested')].inner.is_crate" true
// @count - "$.index[*][?(@.name=='nested')].inner.items[*]" 1 // @count - "$.index[*][?(@.name=='nested')].inner.items[*]" 1
// @has nested.json "$.index[*][?(@.name=='l1')].kind" \"module\" // @is nested.json "$.index[*][?(@.name=='l1')].kind" \"module\"
// @has - "$.index[*][?(@.name=='l1')].inner.is_crate" false // @is - "$.index[*][?(@.name=='l1')].inner.is_crate" false
// @count - "$.index[*][?(@.name=='l1')].inner.items[*]" 2 // @count - "$.index[*][?(@.name=='l1')].inner.items[*]" 2
pub mod l1 { pub mod l1 {
// @has nested.json "$.index[*][?(@.name=='l3')].kind" \"module\" // @is nested.json "$.index[*][?(@.name=='l3')].kind" \"module\"
// @has - "$.index[*][?(@.name=='l3')].inner.is_crate" false // @is - "$.index[*][?(@.name=='l3')].inner.is_crate" false
// @count - "$.index[*][?(@.name=='l3')].inner.items[*]" 1 // @count - "$.index[*][?(@.name=='l3')].inner.items[*]" 1
// @set l3_id = - "$.index[*][?(@.name=='l3')].id"
// @has - "$.index[*][?(@.name=='l1')].inner.items[*]" $l3_id
pub mod l3 { pub mod l3 {
// @has nested.json "$.index[*][?(@.name=='L4')].kind" \"struct\" // @is nested.json "$.index[*][?(@.name=='L4')].kind" \"struct\"
// @has - "$.index[*][?(@.name=='L4')].inner.struct_type" \"unit\" // @is - "$.index[*][?(@.name=='L4')].inner.struct_type" \"unit\"
// @set l4_id = - "$.index[*][?(@.name=='L4')].id"
// @has - "$.index[*][?(@.name=='l3')].inner.items[*]" $l4_id
pub struct L4; pub struct L4;
} }
// @has nested.json "$.index[*][?(@.inner.span=='l3::L4')].kind" \"import\" // @is nested.json "$.index[*][?(@.inner.span=='l3::L4')].kind" \"import\"
// @has - "$.index[*][?(@.inner.span=='l3::L4')].inner.glob" false // @is - "$.index[*][?(@.inner.span=='l3::L4')].inner.glob" false
pub use l3::L4; pub use l3::L4;
} }

View file

@ -9,6 +9,7 @@ pub struct Cache {
root: PathBuf, root: PathBuf,
files: HashMap<PathBuf, String>, files: HashMap<PathBuf, String>,
values: HashMap<PathBuf, Value>, values: HashMap<PathBuf, Value>,
pub variables: HashMap<String, Value>,
last_path: Option<PathBuf>, last_path: Option<PathBuf>,
} }
@ -19,6 +20,7 @@ impl Cache {
root: Path::new(doc_dir).to_owned(), root: Path::new(doc_dir).to_owned(),
files: HashMap::new(), files: HashMap::new(),
values: HashMap::new(), values: HashMap::new(),
variables: HashMap::new(),
last_path: None, last_path: None,
} }
} }

View file

@ -2,6 +2,7 @@ use jsonpath_lib::select;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
use serde_json::Value; use serde_json::Value;
use std::borrow::Cow;
use std::{env, fmt, fs}; use std::{env, fmt, fs};
mod cache; mod cache;
@ -48,13 +49,16 @@ pub struct Command {
pub enum CommandKind { pub enum CommandKind {
Has, Has,
Count, Count,
Is,
Set,
} }
impl CommandKind { impl CommandKind {
fn validate(&self, args: &[String], command_num: usize, lineno: usize) -> bool { fn validate(&self, args: &[String], command_num: usize, lineno: usize) -> bool {
let count = match self { let count = match self {
CommandKind::Has => (1..=3).contains(&args.len()), CommandKind::Has => (1..=3).contains(&args.len()),
CommandKind::Count => 3 == args.len(), CommandKind::Count | CommandKind::Is => 3 == args.len(),
CommandKind::Set => 4 == args.len(),
}; };
if !count { if !count {
@ -83,6 +87,8 @@ impl fmt::Display for CommandKind {
let text = match self { let text = match self {
CommandKind::Has => "has", CommandKind::Has => "has",
CommandKind::Count => "count", CommandKind::Count => "count",
CommandKind::Is => "is",
CommandKind::Set => "set",
}; };
write!(f, "{}", text) write!(f, "{}", text)
} }
@ -127,6 +133,8 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
let cmd = match cmd { let cmd = match cmd {
"has" => CommandKind::Has, "has" => CommandKind::Has,
"count" => CommandKind::Count, "count" => CommandKind::Count,
"is" => CommandKind::Is,
"set" => CommandKind::Set,
_ => { _ => {
print_err(&format!("Unrecognized command name `@{}`", cmd), lineno); print_err(&format!("Unrecognized command name `@{}`", cmd), lineno);
errors = true; errors = true;
@ -180,6 +188,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
/// Performs the actual work of ensuring a command passes. Generally assumes the command /// Performs the actual work of ensuring a command passes. Generally assumes the command
/// is syntactically valid. /// is syntactically valid.
fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> { fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
// FIXME: Be more granular about why, (e.g. syntax error, count not equal)
let result = match command.kind { let result = match command.kind {
CommandKind::Has => { CommandKind::Has => {
match command.args.len() { match command.args.len() {
@ -188,23 +197,15 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
// @has <path> <jsonpath> = check path exists // @has <path> <jsonpath> = check path exists
2 => { 2 => {
let val = cache.get_value(&command.args[0])?; let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
match select(&val, &command.args[1]) { !results.is_empty()
Ok(results) => !results.is_empty(),
Err(_) => false,
}
} }
// @has <path> <jsonpath> <value> = check *any* item matched by path equals value // @has <path> <jsonpath> <value> = check *any* item matched by path equals value
3 => { 3 => {
let val = cache.get_value(&command.args[0])?; let val = cache.get_value(&command.args[0])?;
match select(&val, &command.args[1]) { let results = select(&val, &command.args[1]).unwrap();
Ok(results) => { let pat = string_to_value(&command.args[2], cache);
let pat: Value = serde_json::from_str(&command.args[2]).unwrap(); results.contains(&pat.as_ref())
!results.is_empty() && results.into_iter().any(|val| *val == pat)
}
Err(_) => false,
}
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -215,9 +216,37 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
let expected: usize = command.args[2].parse().unwrap(); let expected: usize = command.args[2].parse().unwrap();
let val = cache.get_value(&command.args[0])?; let val = cache.get_value(&command.args[0])?;
match select(&val, &command.args[1]) { let results = select(&val, &command.args[1]).unwrap();
Ok(results) => results.len() == expected, results.len() == expected
Err(_) => false, }
CommandKind::Is => {
// @has <path> <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
assert_eq!(command.args.len(), 3);
let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
let pat = string_to_value(&command.args[2], cache);
results.len() == 1 && results[0] == pat.as_ref()
}
CommandKind::Set => {
// @set <name> = <path> <jsonpath>
assert_eq!(command.args.len(), 4);
assert_eq!(command.args[1], "=", "Expected an `=`");
let val = cache.get_value(&command.args[2])?;
let results = select(&val, &command.args[3]).unwrap();
assert_eq!(results.len(), 1);
match results.len() {
0 => false,
1 => {
let r = cache.variables.insert(command.args[0].clone(), results[0].clone());
assert!(r.is_none(), "Name collision: {} is duplicated", command.args[0]);
true
}
_ => {
panic!(
"Got multiple results in `@set` for `{}`: {:?}",
&command.args[3], results
);
}
} }
} }
}; };
@ -247,3 +276,11 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
Ok(()) Ok(())
} }
} }
fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> {
if s.starts_with("$") {
Cow::Borrowed(&cache.variables[&s[1..]])
} else {
Cow::Owned(serde_json::from_str(s).unwrap())
}
}