Skip to content

Commit f1da57c

Browse files
committed
Add yaml test runner (#98)
This commit adds a new package to the repository, yaml_test_runner, that 1. download yaml tests from Elasticsearch repository for a given branch 2. uses yaml test files in conjunction with rest specs to generate integration tests for each test defined in yaml. 3. runs tests Having a yaml test runner for the client brings it in line with the other official Elasticsearch clients. Tests are generated in a tests directory in the yaml_test_runner package so that they can be invoked with cargo test. The api_generator package is now exposed as a library to yaml_test_runner package, whilst also still compiling it as a runnable binary to generate the client. This allows the generator modules to be used in the yaml_test_runner where they are used to generate tests, by inspecting the ASTs produced by the api_generator. The typical flow to generate and run tests is 1. Define ELASTICSEARCH_VERSION environment variable export ELASTICSEARCH_VERSION=elasticsearch-oss:7.7.0-SNAPSHOT This is used to determine which test suite to generate, based on whether the version uses the default distribution (xpack suite), or the oss distribution (oss suite). 2. Run yaml_test_runner cargo run -p yaml_test_runner -- \ --branch <elasticsearch branch> \ --token <token> \ --path "<path to rest specs>" where - --branch is the elasticsearch branch to target to download yaml tests - --token is a GitHub access token to use to download the yaml tests using the content API - --path is the path to download rest specs to 3. Run the generated tests cargo test -p yaml_test_runner -- --test-threads=1 The tests must be run synchronously, so a single thread must be used. Other pertinent changes in this commit: * impl From<> for TrackTotalHits for i64 and bool * Define expand_wildcards param as a collection * Handle url serialization of enum collections Updates serialize_coll_qs to handle serializing a collection of types that implement Serialize to a comma separated query string value. This is only needed for enums and strings, but (mis)uses serde_json serialize to achieve this, since serde_urlencoded does not support such a format. We could probably implement a serde Serializer for this later on. * Credentials set before setting any other headers in transport to allow headers to overwrite values. * Update TypeKind::None to TypeKind::Unknown(String) Better handle new types when introduced Closes #19 (cherry picked from commit 27b8ac9)
1 parent ef3dd9f commit f1da57c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4593
-340
lines changed

.ci/run-elasticsearch.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ $volumes = @(
217217
"--volume", "${volume_name}:/usr/share/elasticsearch/data"
218218
)
219219

220-
if (-not ($version -contains "oss")) {
220+
if (-not ($version -match "oss")) {
221221
$environment += @(
222222
"--env", "ELASTIC_PASSWORD=`"$ELASTIC_PASSWORD`"",
223223
"--env", "xpack.license.self_generated.type=trial",
@@ -241,7 +241,7 @@ if (-not ($version -contains "oss")) {
241241
}
242242

243243
$url="http://$NODE_NAME"
244-
if (-not ($version -contains "oss")) {
244+
if (-not ($version -match "oss")) {
245245
$url="https://elastic:$ELASTIC_PASSWORD@$NODE_NAME"
246246
}
247247

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ Cargo.lock
55
.idea
66
.vscode/
77
*.log
8-
yaml_test_runner/
8+
yaml_test_runner/yaml/
9+
yaml_test_runner/tests/oss
10+
yaml_test_runner/tests/xpack
11+
yaml_test_runner/tests/mod.rs

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
33
"api_generator",
4-
"elasticsearch"
4+
"elasticsearch",
5+
"yaml_test_runner"
56
]

api_generator/src/main.rs renamed to api_generator/src/bin/run.rs

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,28 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19+
extern crate api_generator;
1920
extern crate dialoguer;
2021

21-
#[macro_use]
22-
extern crate lazy_static;
23-
24-
#[macro_use]
25-
extern crate quote;
26-
22+
use api_generator::{generator, rest_spec};
2723
use dialoguer::Input;
2824
use std::path::PathBuf;
2925
use std::{
3026
fs::{self, File},
3127
io::Write,
32-
path::Path,
3328
};
3429

35-
mod api_generator;
36-
mod error;
37-
mod rest_spec;
38-
39-
fn main() {
30+
fn main() -> Result<(), failure::Error> {
4031
// This must be run from the src root directory, with cargo run -p api_generator
41-
let download_dir = fs::canonicalize(PathBuf::from("./api_generator/rest_specs")).unwrap();
42-
let generated_dir = fs::canonicalize(PathBuf::from("./elasticsearch/src/generated")).unwrap();
43-
let last_downloaded_version = "./api_generator/last_downloaded_version";
32+
let download_dir = fs::canonicalize(PathBuf::from("./api_generator/rest_specs"))?;
33+
let generated_dir = fs::canonicalize(PathBuf::from("./elasticsearch/src/generated"))?;
34+
let last_downloaded_version =
35+
PathBuf::from("./api_generator/rest_specs/last_downloaded_version");
4436

4537
let mut download_specs = false;
4638
let mut answer = String::new();
47-
let default_branch = if Path::new(last_downloaded_version).exists() {
48-
fs::read_to_string(last_downloaded_version).expect("Could not read branch into string")
39+
let default_branch = if last_downloaded_version.exists() {
40+
fs::read_to_string(&last_downloaded_version)?
4941
} else {
5042
String::from("master")
5143
};
@@ -76,13 +68,9 @@ fn main() {
7668
.interact()
7769
.unwrap();
7870

79-
fs::remove_dir_all(&download_dir).unwrap();
80-
rest_spec::download_specs(&branch, &download_dir);
81-
82-
File::create(last_downloaded_version)
83-
.expect("failed to create last_downloaded_version file")
84-
.write_all(branch.as_bytes())
85-
.expect("unable to write branch to last_downloaded_version file");
71+
fs::remove_dir_all(&download_dir)?;
72+
rest_spec::download_specs(&branch, &download_dir)?;
73+
File::create(&last_downloaded_version)?.write_all(branch.as_bytes())?;
8674
}
8775

8876
// only offer to generate if there are downloaded specs
@@ -109,12 +97,13 @@ fn main() {
10997
if generate_code {
11098
// delete existing generated files if the exist
11199
if generated_dir.exists() {
112-
fs::remove_dir_all(&generated_dir).unwrap();
100+
fs::remove_dir_all(&generated_dir)?;
113101
}
114102

115-
fs::create_dir_all(&generated_dir).unwrap();
116-
117-
api_generator::generate(&branch, &download_dir, &generated_dir).unwrap();
103+
fs::create_dir_all(&generated_dir)?;
104+
generator::generate(&branch, &download_dir, &generated_dir)?;
118105
}
119106
}
107+
108+
Ok(())
120109
}

api_generator/src/api_generator/code_gen/mod.rs renamed to api_generator/src/generator/code_gen/mod.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub mod request;
2222
pub mod root;
2323
pub mod url;
2424

25-
use crate::api_generator::TypeKind;
25+
use crate::generator::TypeKind;
2626
use inflector::Inflector;
2727
use quote::Tokens;
2828
use std::str;
@@ -73,7 +73,7 @@ pub fn parse_expr(input: quote::Tokens) -> syn::Expr {
7373
}
7474

7575
/// Ensures that the name generated is one that is valid for Rust
76-
fn valid_name(s: &str) -> &str {
76+
pub fn valid_name(s: &str) -> &str {
7777
match s {
7878
"type" => "ty",
7979
s => s,
@@ -116,7 +116,7 @@ impl GetPath for syn::Ty {
116116
fn get_path(&self) -> &syn::Path {
117117
match *self {
118118
syn::Ty::Path(_, ref p) => &p,
119-
_ => panic!("Only path types are supported."),
119+
ref p => panic!(format!("Expected syn::Ty::Path, but found {:?}", p)),
120120
}
121121
}
122122
}
@@ -138,21 +138,42 @@ impl<T: GetPath> GetIdent for T {
138138
}
139139

140140
/// Gets the Ty syntax token for a TypeKind
141-
fn typekind_to_ty(name: &str, kind: &TypeKind, required: bool) -> syn::Ty {
141+
/// TODO: This function is serving too many purposes. Refactor it
142+
fn typekind_to_ty(name: &str, kind: &TypeKind, required: bool, fn_arg: bool) -> syn::Ty {
142143
let mut v = String::new();
143144
if !required {
144145
v.push_str("Option<");
145146
}
146147

147148
let str_type = "&'b str";
148149
match kind {
149-
TypeKind::None => v.push_str(str_type),
150-
TypeKind::List => v.push_str(format!("&'b [{}]", str_type).as_ref()),
151-
TypeKind::Enum => v.push_str(name.to_pascal_case().as_str()),
150+
TypeKind::Unknown(_) => v.push_str(str_type),
151+
TypeKind::List => {
152+
v.push_str("&'b [");
153+
v.push_str(str_type);
154+
v.push_str("]");
155+
}
156+
TypeKind::Enum => match name {
157+
// opened https://github.com/elastic/elasticsearch/issues/53212
158+
// to discuss whether this really should be a collection
159+
"expand_wildcards" => {
160+
// Expand wildcards should
161+
v.push_str("&'b [");
162+
v.push_str(name.to_pascal_case().as_str());
163+
v.push_str("]");
164+
}
165+
_ => v.push_str(name.to_pascal_case().as_str()),
166+
},
152167
TypeKind::String => v.push_str(str_type),
153168
TypeKind::Text => v.push_str(str_type),
154169
TypeKind::Boolean => match name {
155-
"track_total_hits" => v.push_str("TrackTotalHits"),
170+
"track_total_hits" => {
171+
if fn_arg {
172+
v.push_str(format!("Into<{}>", name.to_pascal_case()).as_str())
173+
} else {
174+
v.push_str(name.to_pascal_case().as_str())
175+
}
176+
}
156177
_ => v.push_str("bool"),
157178
},
158179
TypeKind::Number => v.push_str("i64"),

api_generator/src/api_generator/code_gen/namespace_clients.rs renamed to api_generator/src/generator/code_gen/namespace_clients.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
use crate::api_generator::*;
20-
use crate::api_generator::code_gen::request::request_builder::RequestBuilder;
21-
use crate::api_generator::code_gen::*;
19+
use crate::generator::code_gen::request::request_builder::RequestBuilder;
20+
use crate::generator::code_gen::*;
21+
use crate::generator::*;
2222
use inflector::Inflector;
2323
use quote::Tokens;
2424
use std::path::PathBuf;

api_generator/src/api_generator/code_gen/params.rs renamed to api_generator/src/generator/code_gen/params.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
use crate::api_generator::*;
19+
use crate::generator::*;
2020
use inflector::Inflector;
2121
use quote::Tokens;
2222
use regex::Regex;

api_generator/src/api_generator/code_gen/request/request_builder.rs renamed to api_generator/src/generator/code_gen/request/request_builder.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
use crate::api_generator::{
19+
use crate::generator::{
2020
code_gen, code_gen::url::enum_builder::EnumBuilder, code_gen::*, ApiEndpoint, HttpMethod, Type,
2121
TypeKind,
2222
};
@@ -25,7 +25,7 @@ use quote::{ToTokens, Tokens};
2525
use reqwest::Url;
2626
use std::path::PathBuf;
2727
use std::{collections::BTreeMap, fs, str};
28-
use syn::{Field, FieldValue, ImplItem};
28+
use syn::{Field, FieldValue, ImplItem, TraitBoundModifier, TyParamBound};
2929

3030
/// Builder that generates the AST for a request builder struct
3131
pub struct RequestBuilder<'a> {
@@ -126,7 +126,8 @@ impl<'a> RequestBuilder<'a> {
126126
let struct_fields = endpoint_params.iter().map(|(param_name, param_type)| {
127127
let field = Self::create_struct_field((param_name, param_type));
128128
let field_rename = lit(param_name);
129-
if param_type.ty == TypeKind::List {
129+
// TODO: we special case expand_wildcards here to be a list, but this should be fixed upstream
130+
if param_type.ty == TypeKind::List || param_name == "expand_wildcards" {
130131
let serialize_with = lit("crate::client::serialize_coll_qs");
131132
quote! {
132133
#[serde(rename = #field_rename, serialize_with = #serialize_with)]
@@ -352,10 +353,36 @@ impl<'a> RequestBuilder<'a> {
352353
/// Creates the AST for a builder fn for a builder impl
353354
fn create_impl_fn(f: (&String, &Type)) -> syn::ImplItem {
354355
let name = valid_name(&f.0).to_lowercase();
356+
let (ty, value_ident, fn_generics) = {
357+
let ty = typekind_to_ty(&f.0, &f.1.ty, true, true);
358+
match ty {
359+
syn::Ty::Path(ref _q, ref p) => {
360+
if p.get_ident().as_ref() == "Into" {
361+
let ty = syn::parse_type("T").unwrap();
362+
let ident = code_gen::ident(format!("{}.into()", &name));
363+
let ty_param = syn::TyParam {
364+
ident: code_gen::ident("T"),
365+
default: None,
366+
attrs: vec![],
367+
bounds: vec![TyParamBound::Trait(
368+
syn::PolyTraitRef {
369+
trait_ref: p.clone(),
370+
bound_lifetimes: vec![],
371+
},
372+
TraitBoundModifier::None,
373+
)],
374+
};
375+
let generics = generics(vec![], vec![ty_param]);
376+
(ty, ident, generics)
377+
} else {
378+
(ty, ident(&name), generics_none())
379+
}
380+
}
381+
_ => (ty, ident(&name), generics_none()),
382+
}
383+
};
355384
let impl_ident = ident(&name);
356385
let field_ident = ident(&name);
357-
let value_ident = ident(&name);
358-
let ty = typekind_to_ty(&f.0, &f.1.ty, true);
359386
let doc_attr = match &f.1.description {
360387
Some(docs) => vec![doc(docs)],
361388
_ => vec![],
@@ -382,7 +409,7 @@ impl<'a> RequestBuilder<'a> {
382409
output: syn::FunctionRetTy::Ty(code_gen::ty("Self")),
383410
variadic: false,
384411
},
385-
generics: generics_none(),
412+
generics: fn_generics,
386413
},
387414
// generates a fn body of the form
388415
// --------
@@ -409,13 +436,11 @@ impl<'a> RequestBuilder<'a> {
409436
enum_builder: &EnumBuilder,
410437
accepts_nd_body: bool,
411438
) -> Tokens {
412-
// TODO: lazy_static! for this?
413439
let mut common_fields: Vec<Field> = common_params
414440
.iter()
415441
.map(Self::create_struct_field)
416442
.collect();
417443

418-
// TODO: lazy_static! for this?
419444
let mut common_builder_fns: Vec<ImplItem> =
420445
common_params.iter().map(Self::create_impl_fn).collect();
421446

@@ -667,7 +692,7 @@ impl<'a> RequestBuilder<'a> {
667692
ident: Some(ident(valid_name(&f.0).to_lowercase())),
668693
vis: syn::Visibility::Inherited,
669694
attrs: vec![],
670-
ty: typekind_to_ty(&f.0, &f.1.ty, false),
695+
ty: typekind_to_ty(&f.0, &f.1.ty, false, false),
671696
}
672697
}
673698

api_generator/src/api_generator/code_gen/root.rs renamed to api_generator/src/generator/code_gen/root.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
use crate::api_generator::*;
20-
use crate::api_generator::code_gen::request::request_builder::RequestBuilder;
21-
use crate::api_generator::code_gen::*;
19+
use crate::generator::code_gen::request::request_builder::RequestBuilder;
20+
use crate::generator::code_gen::*;
21+
use crate::generator::*;
2222
use inflector::Inflector;
2323
use quote::Tokens;
2424
use std::path::PathBuf;

api_generator/src/api_generator/code_gen/url/enum_builder.rs renamed to api_generator/src/generator/code_gen/url/enum_builder.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
* See the License for the specific language governing permissions and
3333
* limitations under the License.
3434
*/
35-
use crate::api_generator::code_gen::url::url_builder::{IntoExpr, UrlBuilder};
36-
use crate::api_generator::{code_gen::*, ApiEndpoint, Path};
35+
use crate::generator::code_gen::url::url_builder::{IntoExpr, UrlBuilder};
36+
use crate::generator::{code_gen::*, ApiEndpoint, Path};
3737
use inflector::Inflector;
3838

3939
/// Builder for request url parts enum
@@ -139,7 +139,7 @@ impl<'a> EnumBuilder<'a> {
139139
ident: None,
140140
vis: syn::Visibility::Inherited,
141141
attrs: vec![],
142-
ty: typekind_to_ty(p, ty, true),
142+
ty: typekind_to_ty(p, ty, true, false),
143143
}
144144
})
145145
.collect(),
@@ -306,15 +306,16 @@ mod tests {
306306
#![cfg_attr(rustfmt, rustfmt_skip)]
307307

308308
use super::*;
309-
use crate::api_generator::{Url, Path, HttpMethod, Body, Deprecated, Type, TypeKind, Documentation, ast_eq};
309+
use crate::generator::{Url, Path, HttpMethod, Body, Deprecated, Type, TypeKind, Documentation, ast_eq};
310310
use std::collections::BTreeMap;
311-
use crate::api_generator::code_gen::url::url_builder::PathString;
311+
use crate::generator::code_gen::url::url_builder::PathString;
312312

313313
#[test]
314314
fn generate_parts_enum_from_endpoint() {
315315
let endpoint = (
316316
"search".to_string(),
317317
ApiEndpoint {
318+
full_name: Some("search".to_string()),
318319
documentation: Documentation {
319320
description: None,
320321
url: None,

0 commit comments

Comments
 (0)