Add check for binary compatibility by building a small sample.

This commit is contained in:
jcamiel 2022-11-25 17:51:51 +01:00
parent 3f2397e6eb
commit 06bf7e6ba5
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
6 changed files with 1570 additions and 1 deletions

View File

@ -37,7 +37,7 @@ jobs:
options: --volume ${{ github.workspace }}:/work:rw --workdir /work --privileged --env CARGO_TERM_COLOR=always
run: gitleaks detect --verbose --config .github/workflows/config/gitleaks.toml
- name: Install Prerequisites
- name: Install prerequisites
run: bin/check/install_prerequisites.sh
- name: Rustfmt
@ -58,6 +58,9 @@ jobs:
- name: Check crates licence
run: python3 bin/check/license.py
- name: Check hurl crate API changes
run: bin/check/compatibility.sh
- name: Check CHANGELOG
run: bin/check/changelog.sh

54
bin/check/compatibility.sh Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
set -eu
get_sample_crate_version() {
crate="$1"
version=$(grep --regexp "^$crate = " contrib/sample/Cargo.toml | cut --delimiter ' ' --field 3 | sed 's/"//g')
echo "$version"
}
get_snapshot_crate_version() {
crate="$1"
version=$(grep --regexp '^version = ' "packages/$crate/Cargo.toml" | cut --delimiter '"' --field 2)
echo "$version"
}
get_major() {
version="$1"
major=$(echo "$version" | cut --delimiter '.' --field 1)
echo "$major"
}
# Extract sample and snapshots versions
hurl_sample_version=$(get_sample_crate_version "hurl")
hurl_sample_major=$(get_major "$hurl_sample_version")
hurl_snapshot_version=$(get_snapshot_crate_version "hurl")
hurl_snapshot_major=$(get_major "$hurl_snapshot_version")
echo "hurl (sample) major=$hurl_sample_major ($hurl_sample_version)"
echo "hurl (SNAPSHOT) major=$hurl_snapshot_major ($hurl_snapshot_version)"
hurl_core_sample_version=$(get_sample_crate_version "hurl_core")
hurl_core_sample_major=$(get_major "$hurl_core_sample_version")
hurl_core_snapshot_version=$(get_snapshot_crate_version "hurl_core")
hurl_core_snapshot_major=$(get_major "$hurl_core_snapshot_version")
echo "hurl_core (sample) major=$hurl_core_sample_major ($hurl_core_sample_version)"
echo "hurl_core (SNAPSHOT) major=$hurl_core_snapshot_major ($hurl_core_snapshot_version)"
if [ "$hurl_sample_major" != "$hurl_snapshot_major" ] && [ "$hurl_core_sample_major" != "$hurl_core_snapshot_major" ];then
echo "Major versions are different, no need to check crates compatibility"
exit 0
else
echo "Major versions are equal, check crates compatibility"
fi
# Replace versions for our sample
sed -i -- "s/hurl = \"[0-9.]*\"/hurl = { version = \"$hurl_snapshot_version\", path = \"..\/..\/packages\/hurl\" }/" contrib/sample/Cargo.toml
sed -i -- "s/hurl_core = \"[0-9.]*\"/hurl_core = { version = \"$hurl_core_snapshot_version\", path = \"..\/..\/packages\/hurl_core\" }/" contrib/sample/Cargo.toml
cd contrib/sample || exit
cargo clean
cargo update
cargo build
cargo run -- hello.hurl

1262
contrib/sample/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
contrib/sample/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "hurl-sample"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
hurl = "1.8.0"
hurl_core = "1.8.0"

12
contrib/sample/hello.hurl Normal file
View File

@ -0,0 +1,12 @@
GET http://localhost:8000/hello
HTTP/* 200
[Asserts]
status == 200
[Captures]
code: status
```Hello World!```
GET http://localhost:8000/xxx
HTTP/* 200

227
contrib/sample/src/main.rs Normal file
View File

@ -0,0 +1,227 @@
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2022 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use hurl::cli::Logger;
use hurl::http::{ContextDir, Header, Request, Response};
use hurl::runner::{AssertResult, Call, CaptureResult, EntryResult, Error, HurlResult};
use hurl::{http, runner};
use hurl_core::parser;
use std::collections::HashMap;
use std::fmt::Debug;
use std::process::exit;
use std::time::Duration;
use std::{env, fs};
/// Run a Hurl file and dumps results.
/// This sample is used to detect public APIs change for Hurl crates.
/// It depends on the last released version (usually the version prior to the SNAPSHOT one).
/// In the CI, this sample is build using SNAPSHOT version and executed.
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() <= 1 {
println!("Missing Hurl file as input");
exit(1);
}
let file_path = &args[1];
let contents = fs::read_to_string(file_path).expect("Should have been able to read the file");
// Parse Hurl file
let hurl_file = parser::parse_hurl_file(&contents).expect("Invalid Hurl file");
// Create an HTTP client
let mut client = http::Client::new(None);
let logger = Logger::new(false, false, file_path, &contents);
// Define runner options
let runner_options = runner::RunnerOptions {
cacert_file: None,
compressed: false,
connect_timeout: Duration::from_secs(300),
context_dir: ContextDir::default(),
cookie_input_file: None,
fail_fast: false,
follow_location: false,
ignore_asserts: false,
insecure: false,
max_redirect: None,
no_proxy: None,
post_entry: None,
pre_entry: None,
proxy: None,
retry: false,
retry_interval: Duration::from_secs(1),
retry_max_count: Some(10),
timeout: Duration::from_secs(300),
to_entry: None,
user: None,
user_agent: None,
verbosity: None,
very_verbose: true,
};
// Set variables
let variables = HashMap::default();
// Run the hurl file
let results = runner::run(
&hurl_file,
file_path,
&mut client,
&runner_options,
&variables,
&logger,
);
print_results(&results);
}
/// Prints a Hurl result
fn print_results(results: &HurlResult) {
let level = 0;
print(level, "file", &results.filename);
print(level, "success", &results.success.to_string());
print(level, "duration", &results.time_in_ms.to_string());
if results.entries.is_empty() {
print(level, "entries", "-");
} else {
print(level, "entries", "");
results.entries.iter().for_each(print_entry);
}
}
/// Prints a Hurl entry.
fn print_entry(entry: &EntryResult) {
let level = 1;
print(level, "index", &entry.entry_index.to_string());
print(level, "duration (ms)", &entry.time_in_ms.to_string());
print(level, "compressed", &entry.compressed.to_string());
if entry.captures.is_empty() {
print(level, "captures", "-");
} else {
print(level, "captures", "");
entry.captures.iter().for_each(print_capture);
}
if entry.asserts.is_empty() {
print(level, "asserts", "-");
} else {
print(level, "asserts", "");
entry.asserts.iter().for_each(print_assert);
}
if entry.errors.is_empty() {
print(level, "errors", "-");
} else {
print(level, "errors", "");
entry.errors.iter().for_each(print_error);
}
if entry.calls.is_empty() {
print(level, "calls", "-");
} else {
print(level, "calls", "");
entry.calls.iter().for_each(print_call);
}
}
/// Prints a capture.
fn print_capture(capture: &CaptureResult) {
let level = 2;
print(level, "name", &capture.name);
print(level, "value", &capture.value.to_string());
}
/// Prints an assert.
fn print_assert(assert: &AssertResult) {
let level = 2;
let kind = match assert {
AssertResult::Version { .. } => "version",
AssertResult::Status { .. } => "status",
AssertResult::Header { .. } => "header",
AssertResult::Body { .. } => "body",
AssertResult::Explicit { .. } => "explicit",
};
print(level, "type", kind);
}
/// Prints an error.
fn print_error(error: &Error) {
let level = 2;
print_dbg(level, "type", &error.inner);
}
/// Prints a call.
fn print_call(call: &Call) {
let level = 2;
print(level, "request", "");
print_request(&call.request);
print(level, "response", "");
print_response(&call.response);
}
/// Prints an HTTP request
fn print_request(request: &Request) {
let level = 3;
print(level, "method", &request.method);
print(level, "url", &request.url);
if request.headers.is_empty() {
print(level, "headers", "-");
} else {
print(level, "headers", "");
request.headers.iter().for_each(print_header);
}
print(level, "body (bytes)", &request.body.len().to_string());
}
/// Prints an HTTP response
fn print_response(response: &Response) {
let level = 3;
print(level, "url", &response.url);
print(level, "version", &response.version.to_string());
print(level, "status", &response.status.to_string());
if response.headers.is_empty() {
print(level, "headers", "-");
} else {
print(level, "headers", "");
response.headers.iter().for_each(print_header);
}
print(level, "body (bytes)", &response.body.len().to_string());
print(
level,
"duration (ms)",
&response.duration.as_millis().to_string(),
);
}
/// Print an HTTP header
fn print_header(header: &Header) {
let level = 4;
print(level, "name", &header.name);
print(level, "value", &header.value);
}
fn print(level: usize, key: &str, value: &str) {
let prefix = " ".repeat(level * 2);
let len = 20 - key.len() - prefix.len();
let space = " ".repeat(len);
println!("{prefix}{key}{space}: {value}");
}
fn print_dbg(level: usize, key: &str, value: impl Debug) {
let prefix = " ".repeat(level * 2);
let len = 20 - key.len() - prefix.len();
let space = " ".repeat(len);
println!("{prefix}{key}{space}: {value:?}");
}