feat: version interceptor

Signed-off-by: discord9 <discord9@163.com>
This commit is contained in:
discord9
2026-03-11 11:34:19 +08:00
parent 896344bd76
commit f02682691b
11 changed files with 434 additions and 343 deletions

View File

@@ -36,12 +36,16 @@ The compat command runs cases in three phases:
## Case markers
Compatibility cases can use sqlness markers:
Compatibility cases can use sqlness marker:
- `-- SQLNESS SINCE <version>`
- `-- SQLNESS TILL <version>`
- `-- SQLNESS VERSION <expression>`
For `since`/`till` skips, runner rewrites the statement before execution to avoid running skipped SQL.
Examples:
- `-- SQLNESS VERSION version >= 0.15.0`
- `-- SQLNESS VERSION version > 1.0.0 AND version < 1.1.0`
For `VERSION` skips, runner rewrites the statement before execution to avoid running skipped SQL.
## Filter behavior

View File

@@ -1,4 +1,4 @@
-- SQLNESS SINCE 0.15.0
-- SQLNESS VERSION version >= 0.15.0
CREATE TABLE granularity_and_false_positive_rate (
ts timestamp time index,
val double
@@ -9,18 +9,17 @@ CREATE TABLE granularity_and_false_positive_rate (
Affected Rows: 0
-- SQLNESS SINCE 99.0.0
-- SQLNESS VERSION version >= 99.0.0
SELECT * FROM __sqlness_since_till_should_not_exist__;
-- SQLNESS_SKIP: target version 1.0.0-beta.1 < 99.0.0
-- SQLNESS_SKIP: target version 1.0.0-beta.1 does not satisfy expression: version >= 99.0.0
-- SQLNESS TILL 0.1.0
-- SQLNESS VERSION version <= 0.1.0
SELECT * FROM __sqlness_since_till_should_not_exist__;
-- SQLNESS_SKIP: target version 1.0.0-beta.1 > 0.1.0
-- SQLNESS_SKIP: target version 1.0.0-beta.1 does not satisfy expression: version <= 0.1.0
-- SQLNESS SINCE 0.1.0
-- SQLNESS TILL 99.0.0
-- SQLNESS VERSION version >= 0.1.0 AND version <= 99.0.0
SELECT 1;
+----------+

View File

@@ -1,4 +1,4 @@
-- SQLNESS SINCE 0.15.0
-- SQLNESS VERSION version >= 0.15.0
CREATE TABLE granularity_and_false_positive_rate (
ts timestamp time index,
val double
@@ -7,12 +7,11 @@ CREATE TABLE granularity_and_false_positive_rate (
"index.false_positive_rate" = "0.01"
);
-- SQLNESS SINCE 99.0.0
-- SQLNESS VERSION version >= 99.0.0
SELECT * FROM __sqlness_since_till_should_not_exist__;
-- SQLNESS TILL 0.1.0
-- SQLNESS VERSION version <= 0.1.0
SELECT * FROM __sqlness_since_till_should_not_exist__;
-- SQLNESS SINCE 0.1.0
-- SQLNESS TILL 99.0.0
-- SQLNESS VERSION version >= 0.1.0 AND version <= 99.0.0
SELECT 1;

View File

@@ -21,6 +21,7 @@ flate2 = "1.0"
hex = "0.4"
local-ip-address = "0.6"
mysql = { version = "26", default-features = false, features = ["minimal", "rustls-tls-ring"] }
nom = "7.1.3"
num_cpus = "1.16"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
semver = "1.0"

View File

@@ -21,7 +21,7 @@ use sqlness::{ConfigBuilder, Runner};
use crate::cmd::bare::ServerAddr;
use crate::env::bare::{StoreConfig, WalConfig};
use crate::env::compat::Env;
use crate::interceptors::{since, till};
use crate::interceptors::version as version_interceptor;
use crate::version::Version;
use crate::{protocol_interceptor, util};
@@ -118,12 +118,10 @@ impl CompatibilityRunner {
Arc::new(protocol_interceptor::ProtocolInterceptorFactory),
);
interceptor_registry.register(
since::PREFIX,
Arc::new(since::SinceInterceptorFactory::new(version.clone())),
);
interceptor_registry.register(
till::PREFIX,
Arc::new(till::TillInterceptorFactory::new(version.clone())),
version_interceptor::PREFIX,
Arc::new(version_interceptor::VersionInterceptorFactory::new(
version.clone(),
)),
);
let env_filter = Self::env_filter(mode_filter);

View File

@@ -316,7 +316,7 @@ impl Env {
let abs_bins_dir = bins_dir
.canonicalize()
.expect("Failed to canonicalize bins_dir");
.unwrap_or_else(|_| panic!("Failed to canonicalize bins_dir: {}", bins_dir.display()));
let mut process = Command::new(abs_bins_dir.join(program))
.current_dir(bins_dir.clone())

View File

@@ -12,5 +12,4 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod since;
pub mod till;
pub mod version;

View File

@@ -1,158 +0,0 @@
// Copyright 2023 Greptime Team
//
// 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 sqlness::interceptor::{Interceptor, InterceptorFactory, InterceptorRef};
use sqlness::{SKIP_MARKER_PREFIX, SqlnessError};
use crate::version::Version;
pub const PREFIX: &str = "SINCE";
/// Interceptor that skips tests if the target version is less than the specified version.
///
/// Usage: `-- SQLNESS SINCE 0.15.0`
///
/// The test will be skipped if `target_version < since_version`.
pub struct SinceInterceptor {
since_version: Version,
target_version: Version,
}
impl SinceInterceptor {
pub fn new(since_version: Version, target_version: Version) -> Self {
Self {
since_version,
target_version,
}
}
fn maybe_rewrite_to_skip_sql(&self, sql: &mut Vec<String>) {
if self.target_version < self.since_version {
let skip_marker = format!("{} {}", SKIP_MARKER_PREFIX, self.skip_reason());
sql.clear();
sql.push(format!("SELECT '{}';", skip_marker));
}
}
fn skip_reason(&self) -> String {
format!(
"target version {} < {}",
self.target_version, self.since_version
)
}
fn normalize_skip_result(&self, result: &mut String) {
if result.contains(SKIP_MARKER_PREFIX) {
*result = format!("{} {}", SKIP_MARKER_PREFIX, self.skip_reason());
}
}
}
impl Interceptor for SinceInterceptor {
fn before_execute(&self, sql: &mut Vec<String>, _ctx: &mut sqlness::QueryContext) {
self.maybe_rewrite_to_skip_sql(sql);
}
fn after_execute(&self, result: &mut String) {
self.normalize_skip_result(result);
}
}
pub struct SinceInterceptorFactory {
target_version: Version,
}
impl SinceInterceptorFactory {
pub fn new(target_version: Version) -> Self {
Self { target_version }
}
}
impl InterceptorFactory for SinceInterceptorFactory {
fn try_new(&self, ctx: &str) -> Result<InterceptorRef, SqlnessError> {
let since_version = Version::parse(ctx).map_err(|e| SqlnessError::InvalidContext {
prefix: PREFIX.to_string(),
msg: format!("Failed to parse version '{}': {:?}", ctx, e),
})?;
Ok(Box::new(SinceInterceptor::new(
since_version,
self.target_version.clone(),
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_before_execute_keeps_sql_when_not_skipped() {
let interceptor = SinceInterceptor::new(
Version::parse("0.15.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut sql = vec!["SELECT 1;".to_string()];
interceptor.maybe_rewrite_to_skip_sql(&mut sql);
assert_eq!(sql, vec!["SELECT 1;"]);
}
#[test]
fn test_before_execute_rewrites_sql_when_skipped() {
let interceptor = SinceInterceptor::new(
Version::parse("0.16.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut sql = vec!["SELECT 1;".to_string()];
interceptor.maybe_rewrite_to_skip_sql(&mut sql);
assert_eq!(sql.len(), 1);
assert!(sql[0].contains(SKIP_MARKER_PREFIX));
assert!(sql[0].contains("target version 0.15.0 < 0.16.0"));
}
#[test]
fn test_after_execute_normalizes_skip_result() {
let interceptor = SinceInterceptor::new(
Version::parse("0.16.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut result = format!(
"+----------------+\n| {} target version 0.15.0 < 0.16.0 |\n+----------------+",
SKIP_MARKER_PREFIX
);
interceptor.normalize_skip_result(&mut result);
assert_eq!(
result,
format!("{} target version 0.15.0 < 0.16.0", SKIP_MARKER_PREFIX)
);
}
#[test]
fn test_after_execute_keeps_non_skip_result() {
let interceptor = SinceInterceptor::new(
Version::parse("0.16.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut result = "Affected Rows: 1".to_string();
interceptor.normalize_skip_result(&mut result);
assert_eq!(result, "Affected Rows: 1");
}
}

View File

@@ -1,158 +0,0 @@
// Copyright 2023 Greptime Team
//
// 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 sqlness::interceptor::{Interceptor, InterceptorFactory, InterceptorRef};
use sqlness::{SKIP_MARKER_PREFIX, SqlnessError};
use crate::version::Version;
pub const PREFIX: &str = "TILL";
/// Interceptor that skips tests if the target version is greater than the specified version.
///
/// Usage: `-- SQLNESS TILL 0.15.0`
///
/// The test will be skipped if `target_version > till_version`.
pub struct TillInterceptor {
till_version: Version,
target_version: Version,
}
impl TillInterceptor {
pub fn new(till_version: Version, target_version: Version) -> Self {
Self {
till_version,
target_version,
}
}
fn maybe_rewrite_to_skip_sql(&self, sql: &mut Vec<String>) {
if self.target_version > self.till_version {
let skip_marker = format!("{} {}", SKIP_MARKER_PREFIX, self.skip_reason());
sql.clear();
sql.push(format!("SELECT '{}';", skip_marker));
}
}
fn skip_reason(&self) -> String {
format!(
"target version {} > {}",
self.target_version, self.till_version
)
}
fn normalize_skip_result(&self, result: &mut String) {
if result.contains(SKIP_MARKER_PREFIX) {
*result = format!("{} {}", SKIP_MARKER_PREFIX, self.skip_reason());
}
}
}
impl Interceptor for TillInterceptor {
fn before_execute(&self, sql: &mut Vec<String>, _ctx: &mut sqlness::QueryContext) {
self.maybe_rewrite_to_skip_sql(sql);
}
fn after_execute(&self, result: &mut String) {
self.normalize_skip_result(result);
}
}
pub struct TillInterceptorFactory {
target_version: Version,
}
impl TillInterceptorFactory {
pub fn new(target_version: Version) -> Self {
Self { target_version }
}
}
impl InterceptorFactory for TillInterceptorFactory {
fn try_new(&self, ctx: &str) -> Result<InterceptorRef, SqlnessError> {
let till_version = Version::parse(ctx).map_err(|e| SqlnessError::InvalidContext {
prefix: PREFIX.to_string(),
msg: format!("Failed to parse version '{}': {:?}", ctx, e),
})?;
Ok(Box::new(TillInterceptor::new(
till_version,
self.target_version.clone(),
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_before_execute_keeps_sql_when_not_skipped() {
let interceptor = TillInterceptor::new(
Version::parse("0.15.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut sql = vec!["SELECT 1;".to_string()];
interceptor.maybe_rewrite_to_skip_sql(&mut sql);
assert_eq!(sql, vec!["SELECT 1;"]);
}
#[test]
fn test_before_execute_rewrites_sql_when_skipped() {
let interceptor = TillInterceptor::new(
Version::parse("0.14.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut sql = vec!["SELECT 1;".to_string()];
interceptor.maybe_rewrite_to_skip_sql(&mut sql);
assert_eq!(sql.len(), 1);
assert!(sql[0].contains(SKIP_MARKER_PREFIX));
assert!(sql[0].contains("target version 0.15.0 > 0.14.0"));
}
#[test]
fn test_after_execute_normalizes_skip_result() {
let interceptor = TillInterceptor::new(
Version::parse("0.14.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut result = format!(
"+----------------+\n| {} target version 0.15.0 > 0.14.0 |\n+----------------+",
SKIP_MARKER_PREFIX
);
interceptor.normalize_skip_result(&mut result);
assert_eq!(
result,
format!("{} target version 0.15.0 > 0.14.0", SKIP_MARKER_PREFIX)
);
}
#[test]
fn test_after_execute_keeps_non_skip_result() {
let interceptor = TillInterceptor::new(
Version::parse("0.14.0").unwrap(),
Version::parse("0.15.0").unwrap(),
);
let mut result = "Affected Rows: 1".to_string();
interceptor.normalize_skip_result(&mut result);
assert_eq!(result, "Affected Rows: 1");
}
}

View File

@@ -0,0 +1,386 @@
// Copyright 2023 Greptime Team
//
// 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 nom::branch::alt;
use nom::bytes::complete::{tag, tag_no_case, take_while1};
use nom::character::complete::multispace0;
use nom::combinator::{all_consuming, map};
use nom::error::Error;
use nom::multi::fold_many0;
use nom::sequence::{delimited, preceded};
use nom::{IResult, Parser as NomParser};
use sqlness::interceptor::{Interceptor, InterceptorFactory, InterceptorRef};
use sqlness::{SKIP_MARKER_PREFIX, SqlnessError};
use crate::version::Version;
pub const PREFIX: &str = "VERSION";
pub struct VersionInterceptor {
target_version: Version,
expression: Expression,
raw_expression: String,
}
impl VersionInterceptor {
pub fn new(target_version: Version, expression: Expression, raw_expression: String) -> Self {
Self {
target_version,
expression,
raw_expression,
}
}
fn skip_reason(&self) -> String {
format!(
"target version {} does not satisfy expression: {}",
self.target_version, self.raw_expression
)
}
fn maybe_rewrite_to_skip_sql(&self, sql: &mut Vec<String>) -> bool {
if self.expression.eval(&self.target_version) {
return false;
}
let skip_marker = format!("{} {}", SKIP_MARKER_PREFIX, self.skip_reason());
sql.clear();
sql.push(format!("SELECT '{}';", skip_marker));
true
}
fn normalize_skip_result(&self, result: &mut String) {
let reason = self.skip_reason();
if result.contains(SKIP_MARKER_PREFIX) && result.contains(reason.as_str()) {
*result = format!("{} {}", SKIP_MARKER_PREFIX, self.skip_reason());
}
}
}
impl Interceptor for VersionInterceptor {
fn before_execute(&self, sql: &mut Vec<String>, _ctx: &mut sqlness::QueryContext) {
self.maybe_rewrite_to_skip_sql(sql);
}
fn after_execute(&self, result: &mut String) {
self.normalize_skip_result(result);
}
}
pub struct VersionInterceptorFactory {
target_version: Version,
}
impl VersionInterceptorFactory {
pub fn new(target_version: Version) -> Self {
Self { target_version }
}
}
impl InterceptorFactory for VersionInterceptorFactory {
fn try_new(&self, ctx: &str) -> Result<InterceptorRef, SqlnessError> {
let raw_expression = ctx.trim();
if raw_expression.is_empty() {
return Err(SqlnessError::InvalidContext {
prefix: PREFIX.to_string(),
msg: "Expression cannot be empty".to_string(),
});
}
let expression =
Parser::parse(raw_expression).map_err(|e| SqlnessError::InvalidContext {
prefix: PREFIX.to_string(),
msg: e,
})?;
Ok(Box::new(VersionInterceptor::new(
self.target_version.clone(),
expression,
raw_expression.to_string(),
)))
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Comparison(ComparisonOp, Version),
And(Box<Expression>, Box<Expression>),
Or(Box<Expression>, Box<Expression>),
Not(Box<Expression>),
}
impl Expression {
fn eval(&self, target: &Version) -> bool {
match self {
Expression::Comparison(op, rhs) => op.eval(target, rhs),
Expression::And(lhs, rhs) => lhs.eval(target) && rhs.eval(target),
Expression::Or(lhs, rhs) => lhs.eval(target) || rhs.eval(target),
Expression::Not(inner) => !inner.eval(target),
}
}
}
#[derive(Debug, Clone)]
pub enum ComparisonOp {
Lt,
Le,
Gt,
Ge,
Eq,
Ne,
}
impl ComparisonOp {
fn eval(&self, lhs: &Version, rhs: &Version) -> bool {
match self {
ComparisonOp::Lt => lhs < rhs,
ComparisonOp::Le => lhs <= rhs,
ComparisonOp::Gt => lhs > rhs,
ComparisonOp::Ge => lhs >= rhs,
ComparisonOp::Eq => lhs == rhs,
ComparisonOp::Ne => lhs != rhs,
}
}
}
struct Parser {
_private: (),
}
impl Parser {
fn parse(input: &str) -> Result<Expression, String> {
match all_consuming(Self::ws(Self::parse_or_expr))(input) {
Ok((_, expression)) => Ok(expression),
Err(err) => Err(format!("Failed to parse VERSION expression: {err:?}")),
}
}
fn ws<'a, O, F>(inner: F) -> impl NomParser<&'a str, O, Error<&'a str>>
where
F: NomParser<&'a str, O, Error<&'a str>>,
{
delimited(multispace0, inner, multispace0)
}
fn parse_or_expr(input: &str) -> IResult<&str, Expression> {
let (input, left) = Self::parse_and_expr(input)?;
fold_many0(
preceded(Self::ws(tag_no_case("OR")), Self::parse_and_expr),
move || left.clone(),
|acc, rhs| Expression::Or(Box::new(acc), Box::new(rhs)),
)
.parse(input)
}
fn parse_and_expr(input: &str) -> IResult<&str, Expression> {
let (input, left) = Self::parse_unary_expr(input)?;
fold_many0(
preceded(Self::ws(tag_no_case("AND")), Self::parse_unary_expr),
move || left.clone(),
|acc, rhs| Expression::And(Box::new(acc), Box::new(rhs)),
)
.parse(input)
}
fn parse_unary_expr(input: &str) -> IResult<&str, Expression> {
let not_parser = map(
preceded(Self::ws(tag_no_case("NOT")), Self::parse_unary_expr),
|expr| Expression::Not(Box::new(expr)),
);
alt((not_parser, Self::parse_primary_expr)).parse(input)
}
fn parse_primary_expr(input: &str) -> IResult<&str, Expression> {
alt((
delimited(Self::ws(tag("(")), Self::parse_or_expr, Self::ws(tag(")"))),
Self::parse_comparison,
))
.parse(input)
}
fn parse_comparison(input: &str) -> IResult<&str, Expression> {
let (input, identifier) = Self::ws(Self::parse_identifier).parse(input)?;
if !identifier.eq_ignore_ascii_case("version") {
return Err(nom::Err::Failure(Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let (input, op) = Self::ws(Self::parse_comparison_op).parse(input)?;
let (input, version_token) = Self::ws(Self::parse_version_literal).parse(input)?;
let version = Version::parse(version_token)
.map_err(|_| nom::Err::Failure(Error::new(input, nom::error::ErrorKind::Fail)))?;
Ok((input, Expression::Comparison(op, version)))
}
fn parse_identifier(input: &str) -> IResult<&str, &str> {
take_while1(|c: char| c.is_ascii_alphabetic() || c == '_').parse(input)
}
fn parse_version_literal(input: &str) -> IResult<&str, &str> {
take_while1(|c: char| !c.is_whitespace() && c != ')').parse(input)
}
fn parse_comparison_op(input: &str) -> IResult<&str, ComparisonOp> {
map(
alt((
tag(">="),
tag("<="),
tag("=="),
tag("!="),
tag(">"),
tag("<"),
)),
|op| match op {
">=" => ComparisonOp::Ge,
"<=" => ComparisonOp::Le,
"==" => ComparisonOp::Eq,
"!=" => ComparisonOp::Ne,
">" => ComparisonOp::Gt,
"<" => ComparisonOp::Lt,
_ => unreachable!(),
},
)
.parse(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser_allows_range_expression() {
let expr = Parser::parse("version > 1.0.0 AND version < 1.1.0").unwrap();
let target = Version::parse("1.0.5").unwrap();
assert!(expr.eval(&target));
let target = Version::parse("1.1.0").unwrap();
assert!(!expr.eval(&target));
}
#[test]
fn test_parser_honors_parentheses_and_not() {
let expr = Parser::parse("NOT (version < 1.0.0 OR version >= 2.0.0)").unwrap();
assert!(expr.eval(&Version::parse("1.5.0").unwrap()));
assert!(!expr.eval(&Version::parse("0.9.9").unwrap()));
assert!(!expr.eval(&Version::parse("2.0.0").unwrap()));
}
#[test]
fn test_parser_supports_current_literal() {
let expr = Parser::parse("version == current").unwrap();
assert!(expr.eval(&Version::Current));
}
#[test]
fn test_parser_rejects_invalid_identifier() {
assert!(Parser::parse("target > 1.0.0").is_err());
}
#[test]
fn test_parser_rejects_invalid_version_literal() {
assert!(Parser::parse("version > nope").is_err());
}
#[test]
fn test_before_execute_keeps_sql_when_expression_matches() {
let expr = Parser::parse("version >= 0.15.0").unwrap();
let interceptor = VersionInterceptor::new(
Version::parse("0.15.0").unwrap(),
expr,
"version >= 0.15.0".to_string(),
);
let mut sql = vec!["SELECT 1;".to_string()];
let skipped = interceptor.maybe_rewrite_to_skip_sql(&mut sql);
assert!(!skipped);
assert_eq!(sql, vec!["SELECT 1;"]);
}
#[test]
fn test_before_execute_rewrites_sql_when_expression_not_match() {
let expr = Parser::parse("version >= 0.16.0").unwrap();
let interceptor = VersionInterceptor::new(
Version::parse("0.15.0").unwrap(),
expr,
"version >= 0.16.0".to_string(),
);
let mut sql = vec!["SELECT 1;".to_string()];
let skipped = interceptor.maybe_rewrite_to_skip_sql(&mut sql);
assert!(skipped);
assert_eq!(sql.len(), 1);
assert!(sql[0].contains(SKIP_MARKER_PREFIX));
assert!(
sql[0].contains("target version 0.15.0 does not satisfy expression: version >= 0.16.0")
);
}
#[test]
fn test_after_execute_normalizes_skip_result_when_skipped() {
let expr = Parser::parse("version >= 0.16.0").unwrap();
let interceptor = VersionInterceptor::new(
Version::parse("0.15.0").unwrap(),
expr,
"version >= 0.16.0".to_string(),
);
let mut result = format!(
"+----------------+\n| {} {} |\n+----------------+",
SKIP_MARKER_PREFIX,
interceptor.skip_reason()
);
interceptor.after_execute(&mut result);
assert_eq!(
result,
format!(
"{} target version 0.15.0 does not satisfy expression: version >= 0.16.0",
SKIP_MARKER_PREFIX
)
);
}
#[test]
fn test_after_execute_does_not_rewrite_when_not_skipped() {
let expr = Parser::parse("version <= 0.15.0").unwrap();
let interceptor = VersionInterceptor::new(
Version::parse("0.15.0").unwrap(),
expr,
"version <= 0.15.0".to_string(),
);
let expected = format!("{} some other reason", SKIP_MARKER_PREFIX);
let mut result = expected.clone();
interceptor.after_execute(&mut result);
assert_eq!(result, expected);
}
#[test]
fn test_factory_rejects_empty_expression() {
let factory = VersionInterceptorFactory::new(Version::parse("0.15.0").unwrap());
let err = match factory.try_new(" ") {
Ok(_) => panic!("expected empty expression to fail"),
Err(err) => err,
};
let msg = err.to_string();
assert!(msg.contains("Expression cannot be empty"));
}
}

View File

@@ -448,7 +448,28 @@ pub fn get_binary_dir(mode: &str) -> PathBuf {
workspace_root.push("target");
workspace_root.push(mode);
workspace_root
if workspace_root.is_dir() {
workspace_root
} else {
// get build.target-dir from cargo config get "build.target-dir" -Z unstable-options --format json-value
let output = Command::new("cargo")
.args([
"config",
"get",
"build.target-dir",
"-Z",
"unstable-options",
"--format",
"json-value",
])
.output()
.expect("Failed to execute cargo metadata");
let target_dir =
String::from_utf8(output.stdout).expect("Failed to parse cargo config output");
let mut target_dir_path = PathBuf::from(target_dir.trim().trim_matches('\"'));
target_dir_path.push(mode);
target_dir_path
}
}
/// Spin-waiting a socket address is available, or timeout.