Skip to content

Commit 9dd32b4

Browse files
committed
Download yaml tests from tarball
This commit updates how yaml tests are downloaded by streaming the tarball for a specific branch, and extracting yaml tests from the tarball. Not only is this much faster than downloading separate files, it removes the need for a GitHub access token as anonymous access limits are likely sufficient. Don't download rest specs when running yaml_test_runner as the checked in rest specs will be those that have been used to generate the client, and therefor should be the ones used when generating tests using the client.
1 parent e08d837 commit 9dd32b4

File tree

4 files changed

+89
-225
lines changed

4 files changed

+89
-225
lines changed

.ci/run-repository.sh

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
# STACK_VERSION -- version e.g Major.Minor.Patch(-Prelease)
66
# TEST_SUITE -- which test suite to run: oss or xpack
77
# ELASTICSEARCH_URL -- The url at which elasticsearch is reachable, a default is composed based on STACK_VERSION and TEST_SUITE
8-
# BRANCH -- Elasticsearch branch from which to pull the YAML test files
98
# RUST_TOOLCHAIN -- Rust toolchain version to compile and run tests
10-
# TOKEN -- GitHub access token used to download the YAML files from the GitHub API
119
script_path=$(dirname $(realpath -s $0))
1210
source $script_path/functions/imports.sh
1311
set -euo pipefail
1412

1513
RUST_TOOLCHAIN=${RUST_TOOLCHAIN-nightly-2020-06-09}
16-
TOKEN=${TOKEN-}
1714
ELASTICSEARCH_URL=${ELASTICSEARCH_URL-"$elasticsearch_url"}
1815
elasticsearch_container=${elasticsearch_container-}
1916

@@ -31,16 +28,16 @@ echo -e "\033[1m>>>>> Run [elastic/elasticsearch-rs container] >>>>>>>>>>>>>>>>>
3128

3229
repo=$(realpath $(dirname $(realpath -s $0))/../)
3330

31+
# ES_TEST_SERVER en var is needed for cargo tests
3432
docker run \
3533
--network=${network_name} \
3634
--env "ES_TEST_SERVER=${ELASTICSEARCH_URL}" \
37-
--env "TOKEN=${TOKEN}" \
3835
--name test-runner \
3936
--volume ${repo}/test_results:/usr/src/elasticsearch-rs/test_results \
4037
--rm \
4138
elastic/elasticsearch-rs \
4239
/bin/bash -c \
43-
"cargo run -p yaml_test_runner -- -u \"${ELASTICSEARCH_URL}\" -p \"api_generator/rest_specs\"; \\
40+
"cargo run -p yaml_test_runner -- -u \"${ELASTICSEARCH_URL}\"; \\
4441
mkdir -p test_results; \\
4542
cargo test -p yaml_test_runner -- --test-threads=1 -Z unstable-options --format json | tee test_results/results.json; \\
4643
cat test_results/results.json | cargo2junit > test_results/junit.xml"

yaml_test_runner/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ syn = { version = "~0.11", features = ["full"] }
3131
sysinfo = "0.9.6"
3232
url = "2.1.1"
3333
yaml-rust = "0.4.3"
34+
tar = "~0.4"
35+
flate2 = "~1"
36+
globset = "~0.4"
3437

3538
[dev-dependencies]
3639
tokio = { version = "0.2.0", default-features = false, features = ["macros", "tcp", "time"] }

yaml_test_runner/src/github.rs

Lines changed: 53 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -16,93 +16,64 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19+
use flate2::read::GzDecoder;
20+
use globset::Glob;
1921
use io::Write;
20-
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
21-
use serde::Deserialize;
22-
use std::{error::Error as StdError, fmt::Formatter, fs, fs::File, io, path::PathBuf};
23-
24-
struct YamlTestSuite {
25-
dir: String,
26-
branch: String,
27-
url: String,
28-
}
29-
30-
#[derive(Deserialize, Debug)]
31-
struct Links {
32-
#[serde(rename = "self")]
33-
self_link: String,
34-
git: String,
35-
html: String,
36-
}
37-
38-
#[derive(Deserialize, Debug)]
39-
struct GitHubContent {
40-
name: String,
41-
path: String,
42-
sha: String,
43-
size: i32,
44-
url: String,
45-
html_url: String,
46-
git_url: String,
47-
download_url: Option<String>,
48-
#[serde(rename = "type")]
49-
ty: String,
50-
#[serde(rename = "_links")]
51-
links: Links,
52-
}
22+
use reqwest::{
23+
header::{HeaderMap, HeaderValue, USER_AGENT},
24+
Response,
25+
};
26+
use std::{fs, fs::File, io, path::PathBuf};
27+
use tar::{Archive, Entry};
5328

5429
/// Downloads the yaml tests if not already downloaded
55-
pub fn download_test_suites(
56-
token: &str,
57-
branch: &str,
58-
download_dir: &PathBuf,
59-
) -> Result<(), failure::Error> {
30+
pub fn download_test_suites(branch: &str, download_dir: &PathBuf) -> Result<(), failure::Error> {
6031
let mut last_downloaded_version = download_dir.clone();
6132
last_downloaded_version.push("last_downloaded_version");
6233
if last_downloaded_version.exists() {
6334
let version = fs::read_to_string(&last_downloaded_version)
6435
.expect("Unable to read last_downloaded_version of yaml tests");
6536
if version == branch {
66-
info!("yaml tests for branch {} already downloaded", branch);
37+
info!("Already downloaded yaml tests from {}", branch);
6738
return Ok(());
6839
}
6940
}
7041

71-
let test_suite_map = [
72-
("oss".to_string(), "https://api.github.com/repos/elastic/elasticsearch/contents/rest-api-spec/src/main/resources/rest-api-spec/test".to_string()),
73-
("xpack".to_string(), "https://api.github.com/repos/elastic/elasticsearch/contents/x-pack/plugin/src/test/resources/rest-api-spec/test".to_string())];
74-
75-
let test_suites: Vec<YamlTestSuite> = test_suite_map
76-
.iter()
77-
.map(|(dir, template_url)| {
78-
let url = format!("{}?ref={}", template_url, branch);
79-
YamlTestSuite {
80-
dir: dir.to_string(),
81-
branch: branch.to_string(),
82-
url,
83-
}
84-
})
85-
.collect();
86-
42+
info!("Downloading yaml tests from {}", branch);
43+
let url = format!(
44+
"https://api.github.com/repos/elastic/elasticsearch/tarball/{}",
45+
branch
46+
);
8747
let mut headers = HeaderMap::new();
88-
let token_value = format!("token {}", token);
89-
headers.append(AUTHORIZATION, HeaderValue::from_str(&token_value)?);
48+
headers.append(
49+
USER_AGENT,
50+
HeaderValue::from_str("elasticsearch-rs/yaml_test_runner")?,
51+
);
9052
let client = reqwest::ClientBuilder::new()
9153
.default_headers(headers)
9254
.build()
9355
.unwrap();
9456

95-
// delete existing yaml tests
96-
if download_dir.exists() {
97-
fs::remove_dir_all(&download_dir)?;
98-
}
99-
100-
fs::create_dir_all(download_dir)?;
101-
102-
for suite in test_suites {
103-
download_tests(&client, &suite, &download_dir)?;
57+
let response = client.get(&url).send()?;
58+
let tar = GzDecoder::new(response);
59+
let mut archive = Archive::new(tar);
60+
61+
let oss_test = Glob::new("**/rest-api-spec/src/main/resources/rest-api-spec/test/**/*.yml")?
62+
.compile_matcher();
63+
let xpack_test = Glob::new("**/x-pack/plugin/src/test/resources/rest-api-spec/test/**/*.yml")?
64+
.compile_matcher();
65+
66+
for entry in archive.entries()? {
67+
let file = entry?;
68+
let path = file.path()?;
69+
if oss_test.is_match(&path) {
70+
write_test_file(download_dir, "oss", file)?;
71+
} else if xpack_test.is_match(&path) {
72+
write_test_file(download_dir, "xpack", file)?;
73+
}
10474
}
10575

76+
info!("Downloaded yaml tests from {}", &branch);
10677
File::create(last_downloaded_version)
10778
.expect("failed to create last_downloaded_version file")
10879
.write_all(branch.as_bytes())
@@ -111,115 +82,25 @@ pub fn download_test_suites(
11182
Ok(())
11283
}
11384

114-
fn download_tests(
115-
client: &reqwest::Client,
116-
suite: &YamlTestSuite,
85+
fn write_test_file(
11786
download_dir: &PathBuf,
118-
) -> Result<(), DownloadError> {
119-
let suite_dir = {
120-
let mut d = download_dir.clone();
121-
d.push(&suite.dir);
122-
d
87+
suite_dir: &str,
88+
mut entry: Entry<GzDecoder<Response>>,
89+
) -> Result<(), failure::Error> {
90+
let path = entry.path()?;
91+
92+
let mut dir = {
93+
let mut dir = download_dir.clone();
94+
dir.push(suite_dir);
95+
let parent = path.parent().unwrap().file_name().unwrap();
96+
dir.push(parent);
97+
dir
12398
};
12499

125-
fs::create_dir_all(&suite_dir)?;
126-
info!("Downloading {} tests from {}", &suite.dir, &suite.branch);
127-
download(client, &suite.url, &suite_dir)?;
128-
info!(
129-
"Done downloading {} tests from {}",
130-
&suite.dir, &suite.branch
131-
);
100+
fs::create_dir_all(&dir)?;
101+
dir.push(path.file_name().unwrap());
102+
let mut file = File::create(&dir)?;
103+
io::copy(&mut entry, &mut file)?;
132104

133105
Ok(())
134106
}
135-
136-
fn download(
137-
client: &reqwest::Client,
138-
url: &str,
139-
download_dir: &PathBuf,
140-
) -> Result<(), DownloadError> {
141-
let mut response = client.get(url).send()?;
142-
143-
let remaining_rate_limit: i32 = response
144-
.headers()
145-
.get("X-RateLimit-Remaining")
146-
.unwrap()
147-
.to_str()
148-
.unwrap()
149-
.parse()
150-
.unwrap();
151-
152-
if remaining_rate_limit < 10 {
153-
warn!("Remaining rate limit: {}", remaining_rate_limit);
154-
}
155-
156-
let contents: Vec<GitHubContent> = response.json()?;
157-
for content in contents {
158-
let content_path = {
159-
let mut d = download_dir.clone();
160-
d.push(&content.name);
161-
d
162-
};
163-
164-
match content.ty.as_str() {
165-
"file" => {
166-
let mut file = File::create(content_path)?;
167-
// no need to send the token for downloading content
168-
let mut file_response = reqwest::get(&content.download_url.unwrap())?;
169-
io::copy(&mut file_response, &mut file)?;
170-
}
171-
"dir" => {
172-
fs::create_dir_all(&content_path)?;
173-
download(client, &content.url, &content_path)?;
174-
}
175-
t => {
176-
return Err(DownloadError::InvalidType(format!(
177-
"Unexpected GitHub content type: {}",
178-
t
179-
)))
180-
}
181-
}
182-
}
183-
184-
Ok(())
185-
}
186-
187-
#[derive(Debug)]
188-
pub enum DownloadError {
189-
IoErr(io::Error),
190-
HttpError(reqwest::Error),
191-
InvalidType(String),
192-
}
193-
194-
impl std::fmt::Display for DownloadError {
195-
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
196-
match self {
197-
DownloadError::IoErr(err) => write!(f, "IoErr {}", err),
198-
DownloadError::HttpError(err) => write!(f, "HttpError {}", err),
199-
DownloadError::InvalidType(s) => write!(f, "InvalidType {}", s),
200-
}
201-
}
202-
}
203-
204-
impl StdError for DownloadError {
205-
#[allow(warnings)]
206-
fn description(&self) -> &str {
207-
match self {
208-
DownloadError::IoErr(err) => err.description(),
209-
DownloadError::HttpError(err) => err.description(),
210-
DownloadError::InvalidType(s) => s.as_ref(),
211-
}
212-
}
213-
}
214-
215-
impl From<io::Error> for DownloadError {
216-
fn from(e: io::Error) -> Self {
217-
DownloadError::IoErr(e)
218-
}
219-
}
220-
221-
impl From<reqwest::Error> for DownloadError {
222-
fn from(e: reqwest::Error) -> Self {
223-
DownloadError::HttpError(e)
224-
}
225-
}

0 commit comments

Comments
 (0)