Support negative matches

This adds support for != and !~ operators
giving more flexibility in comparing values
This commit is contained in:
Kamil Trzciński 2019-04-19 13:17:42 +02:00
parent a59b97d74b
commit 8fa1ab4c33
9 changed files with 221 additions and 6 deletions

View file

@ -0,0 +1,5 @@
---
title: Support negative matches
merge_request:
author:
type: added

View file

@ -434,8 +434,9 @@ Below you can find supported syntax reference:
1. Equality matching using a string
> Example: `$VARIABLE == "some value"`
> Example: `$VARIABLE != "some value"` _(added in 11.11)_
You can use equality operator `==` to compare a variable content to a
You can use equality operator `==` or `!=` to compare a variable content to a
string. We support both, double quotes and single quotes to define a string
value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
are supported. `"some value" == $VARIABLE` is correct too.
@ -443,22 +444,26 @@ Below you can find supported syntax reference:
1. Checking for an undefined value
> Example: `$VARIABLE == null`
> Example: `$VARIABLE != null` _(added in 11.11)_
It sometimes happens that you want to check whether a variable is defined
or not. To do that, you can compare a variable to `null` keyword, like
`$VARIABLE == null`. This expression is going to evaluate to truth if
variable is not defined.
variable is not defined when `==` is used, or to falsey if `!=` is used.
1. Checking for an empty variable
> Example: `$VARIABLE == ""`
> Example: `$VARIABLE != ""` _(added in 11.11)_
If you want to check whether a variable is defined, but is empty, you can
simply compare it against an empty string, like `$VAR == ''`.
simply compare it against an empty string, like `$VAR == ''` or non-empty
string `$VARIABLE != ""`.
1. Comparing two variables
> Example: `$VARIABLE_1 == $VARIABLE_2`
> Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_
It is possible to compare two variables. This is going to compare values
of these variables.
@ -477,9 +482,11 @@ Below you can find supported syntax reference:
1. Pattern matching _(added in 11.0)_
> Example: `$VARIABLE =~ /^content.*/`
> Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
It is possible perform pattern matching against a variable and regular
expression. Expression like this evaluates to truth if matches are found.
expression. Expression like this evaluates to truth if matches are found
when using `=~`. It evaluates to truth if matches are not found when `!~` is used.
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive.

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class NotEquals < Lexeme::Operator
PATTERN = /!=/.freeze
def initialize(left, right)
@left = left
@right = right
end
def evaluate(variables = {})
@left.evaluate(variables) != @right.evaluate(variables)
end
def self.build(_value, behind, ahead)
new(behind, ahead)
end
end
end
end
end
end
end

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class NotMatches < Lexeme::Operator
PATTERN = /\!~/.freeze
def initialize(left, right)
@left = left
@right = right
end
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
regexp.scan(text.to_s).none?
end
def self.build(_value, behind, ahead)
new(behind, ahead)
end
end
end
end
end
end
end

View file

@ -15,7 +15,9 @@ module Gitlab
Expression::Lexeme::Pattern,
Expression::Lexeme::Null,
Expression::Lexeme::Equals,
Expression::Lexeme::Matches
Expression::Lexeme::Matches,
Expression::Lexeme::NotEquals,
Expression::Lexeme::NotMatches
].freeze
MAX_TOKENS = 100

View file

@ -8,13 +8,24 @@ module Gitlab
StatementError = Class.new(Expression::ExpressionError)
GRAMMAR = [
# presence matchers
%w[variable],
# positive matchers
%w[variable equals string],
%w[variable equals variable],
%w[variable equals null],
%w[string equals variable],
%w[null equals variable],
%w[variable matches pattern]
%w[variable matches pattern],
# negative matchers
%w[variable notequals string],
%w[variable notequals variable],
%w[variable notequals null],
%w[string notequals variable],
%w[null notequals variable],
%w[variable notmatches pattern]
].freeze
def initialize(statement, variables = {})

View file

@ -0,0 +1,39 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
let(:left) { double('left') }
let(:right) { double('right') }
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('!=', left, right))
.to be_a(described_class)
end
end
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
end
end
describe '#evaluate' do
it 'returns true when left and right are not equal' do
allow(left).to receive(:evaluate).and_return(1)
allow(right).to receive(:evaluate).and_return(2)
operator = described_class.new(left, right)
expect(operator.evaluate(VARIABLE: 3)).to eq true
end
it 'returns false when left and right are equal' do
allow(left).to receive(:evaluate).and_return(1)
allow(right).to receive(:evaluate).and_return(1)
operator = described_class.new(left, right)
expect(operator.evaluate(VARIABLE: 3)).to eq false
end
end
end

View file

@ -0,0 +1,80 @@
require 'fast_spec_helper'
require_dependency 're2'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
let(:left) { double('left') }
let(:right) { double('right') }
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('!~', left, right))
.to be_a(described_class)
end
end
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
end
end
describe '#evaluate' do
it 'returns true when left and right do not match' do
allow(left).to receive(:evaluate).and_return('my-string')
allow(right).to receive(:evaluate)
.and_return(Gitlab::UntrustedRegexp.new('something'))
operator = described_class.new(left, right)
expect(operator.evaluate).to eq true
end
it 'returns false when left and right match' do
allow(left).to receive(:evaluate).and_return('my-awesome-string')
allow(right).to receive(:evaluate)
.and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
operator = described_class.new(left, right)
expect(operator.evaluate).to eq false
end
it 'supports matching against a nil value' do
allow(left).to receive(:evaluate).and_return(nil)
allow(right).to receive(:evaluate)
.and_return(Gitlab::UntrustedRegexp.new('pattern'))
operator = described_class.new(left, right)
expect(operator.evaluate).to eq true
end
it 'supports multiline strings' do
allow(left).to receive(:evaluate).and_return <<~TEXT
My awesome contents
My-text-string!
TEXT
allow(right).to receive(:evaluate)
.and_return(Gitlab::UntrustedRegexp.new('text-string'))
operator = described_class.new(left, right)
expect(operator.evaluate).to eq false
end
it 'supports regexp flags' do
allow(left).to receive(:evaluate).and_return <<~TEXT
My AWESOME content
TEXT
allow(right).to receive(:evaluate)
.and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
operator = described_class.new(left, right)
expect(operator.evaluate).to eq false
end
end
end

View file

@ -101,6 +101,18 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
"$EMPTY_VARIABLE =~ /var.*/" | false
"$UNDEFINED_VARIABLE =~ /var.*/" | false
"$PRESENT_VARIABLE =~ /VAR.*/i" | true
'$PRESENT_VARIABLE != "my variable"' | false
'"my variable" != $PRESENT_VARIABLE' | false
'$PRESENT_VARIABLE != null' | true
'$EMPTY_VARIABLE != null' | true
'"" != $EMPTY_VARIABLE' | false
'$UNDEFINED_VARIABLE != null' | false
'null != $UNDEFINED_VARIABLE' | false
"$PRESENT_VARIABLE !~ /var.*e$/" | false
"$PRESENT_VARIABLE !~ /^var.*/" | true
"$EMPTY_VARIABLE !~ /var.*/" | true
"$UNDEFINED_VARIABLE !~ /var.*/" | true
"$PRESENT_VARIABLE !~ /VAR.*/i" | false
end
with_them do