summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-03-01 15:44:51 +0000
committerKamil Trzciński <ayufan@ayufan.eu>2018-03-01 15:44:51 +0000
commit461960262d51c719994f0f4236f79dfa1fc0f28f (patch)
tree395ae2933de16af5cce692416325ecfa68e8ec99 /spec
parentd412b90af0ff59c6c379fb83727cc23043c42cdc (diff)
parent511046f9eb8366f3b5fee44b66b763069de6829a (diff)
downloadgitlab-ce-461960262d51c719994f0f4236f79dfa1fc0f28f.tar.gz
Merge branch 'feature/gb/pipeline-variable-expressions' into 'master'
Pipeline variables expressions See merge request gitlab-org/gitlab-ce!17261
Diffstat (limited to 'spec')
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb92
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/token_spec.rb45
8 files changed, 423 insertions, 0 deletions
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
new file mode 100644
index 00000000000..019a2ed184d
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals 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 false 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 false
+ end
+
+ it 'returns true 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 true
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
new file mode 100644
index 00000000000..b5a59929e11
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('null'))
+ .to be_a(described_class)
+ end
+ end
+
+ describe '.type' do
+ it 'is a value lexeme' do
+ expect(described_class.type).to eq :value
+ end
+ end
+
+ describe '#evaluate' do
+ it 'always evaluates to `nil`' do
+ expect(described_class.new('null').evaluate).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
new file mode 100644
index 00000000000..86234dfb9e5
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('"my string"'))
+ .to be_a(described_class)
+ end
+ end
+
+ describe '.type' do
+ it 'is a value lexeme' do
+ expect(described_class.type).to eq :value
+ end
+ end
+
+ describe '.scan' do
+ context 'when using double quotes' do
+ it 'correctly identifies string token' do
+ scanner = StringScanner.new('"some string"')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate).to eq 'some string'
+ end
+ end
+
+ context 'when using single quotes' do
+ it 'correctly identifies string token' do
+ scanner = StringScanner.new("'some string 2'")
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate).to eq 'some string 2'
+ end
+ end
+
+ context 'when there are mixed quotes in the string' do
+ it 'is a greedy scanner for double quotes' do
+ scanner = StringScanner.new('"some string" "and another one"')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate).to eq 'some string'
+ end
+
+ it 'is a greedy scanner for single quotes' do
+ scanner = StringScanner.new("'some string' 'and another one'")
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate).to eq 'some string'
+ end
+
+ it 'allows to use single quotes inside double quotes' do
+ scanner = StringScanner.new(%("some ' string"))
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate).to eq "some ' string"
+ end
+
+ it 'allow to use double quotes inside single quotes' do
+ scanner = StringScanner.new(%('some " string'))
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate).to eq 'some " string'
+ end
+ end
+ end
+
+ describe '#evaluate' do
+ it 'returns string value it is is present' do
+ string = described_class.new('my string')
+
+ expect(string.evaluate).to eq 'my string'
+ end
+
+ it 'returns an empty string if it is empty' do
+ string = described_class.new('')
+
+ expect(string.evaluate).to eq ''
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
new file mode 100644
index 00000000000..599a5411881
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('$VARIABLE'))
+ .to be_a(described_class)
+ end
+ end
+
+ describe '.type' do
+ it 'is a value lexeme' do
+ expect(described_class.type).to eq :value
+ end
+ end
+
+ describe '#evaluate' do
+ it 'returns variable value if it is defined' do
+ variable = described_class.new('VARIABLE')
+
+ expect(variable.evaluate(VARIABLE: 'my variable'))
+ .to eq 'my variable'
+ end
+
+ it 'allows to use a string as a variable key too' do
+ variable = described_class.new('VARIABLE')
+
+ expect(variable.evaluate('VARIABLE' => 'my variable'))
+ .to eq 'my variable'
+ end
+
+ it 'returns nil if it is not defined' do
+ variable = described_class.new('VARIABLE')
+
+ expect(variable.evaluate(OTHER: 'variable')).to be_nil
+ end
+
+ it 'returns an empty string if it is empty' do
+ variable = described_class.new('VARIABLE')
+
+ expect(variable.evaluate(VARIABLE: '')).to eq ''
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
new file mode 100644
index 00000000000..230ceeb07f8
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexer do
+ let(:token_class) do
+ Gitlab::Ci::Pipeline::Expression::Token
+ end
+
+ describe '#tokens' do
+ it 'tokenss single value' do
+ tokens = described_class.new('$VARIABLE').tokens
+
+ expect(tokens).to be_one
+ expect(tokens).to all(be_an_instance_of(token_class))
+ end
+
+ it 'does ignore whitespace characters' do
+ tokens = described_class.new("\t$VARIABLE ").tokens
+
+ expect(tokens).to be_one
+ expect(tokens).to all(be_an_instance_of(token_class))
+ end
+
+ it 'tokenss multiple values of the same token' do
+ tokens = described_class.new("$VARIABLE1 $VARIABLE2").tokens
+
+ expect(tokens.size).to eq 2
+ expect(tokens).to all(be_an_instance_of(token_class))
+ end
+
+ it 'tokenss multiple values with different tokens' do
+ tokens = described_class.new('$VARIABLE "text" "value"').tokens
+
+ expect(tokens.size).to eq 3
+ expect(tokens.first.value).to eq '$VARIABLE'
+ expect(tokens.second.value).to eq '"text"'
+ expect(tokens.third.value).to eq '"value"'
+ end
+
+ it 'tokenss tokens and operators' do
+ tokens = described_class.new('$VARIABLE == "text"').tokens
+
+ expect(tokens.size).to eq 3
+ expect(tokens.first.value).to eq '$VARIABLE'
+ expect(tokens.second.value).to eq '=='
+ expect(tokens.third.value).to eq '"text"'
+ end
+
+ it 'limits statement to specified amount of tokens' do
+ lexer = described_class.new("$V1 $V2 $V3 $V4", max_tokens: 3)
+
+ expect { lexer.tokens }
+ .to raise_error described_class::SyntaxError
+ end
+
+ it 'raises syntax error in case of finding unknown tokens' do
+ lexer = described_class.new('$V1 123 $V2')
+
+ expect { lexer.tokens }
+ .to raise_error described_class::SyntaxError
+ end
+ end
+
+ describe '#lexemes' do
+ it 'returns an array of syntax lexemes' do
+ lexer = described_class.new('$VAR "text"')
+
+ expect(lexer.lexemes).to eq %w[variable string]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
new file mode 100644
index 00000000000..e8e6f585310
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Parser do
+ describe '#tree' do
+ context 'when using operators' do
+ it 'returns a reverse descent parse tree' do
+ expect(described_class.seed('$VAR1 == "123" == $VAR2').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
+ end
+ end
+
+ context 'when using a single token' do
+ it 'returns a single token instance' do
+ expect(described_class.seed('$VAR').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
+ end
+ end
+
+ context 'when expression is empty' do
+ it 'returns a null token' do
+ expect(described_class.seed('').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
new file mode 100644
index 00000000000..472a58599d8
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Statement do
+ let(:pipeline) { build(:ci_pipeline) }
+
+ subject do
+ described_class.new(text, pipeline)
+ end
+
+ before do
+ pipeline.variables.build([key: 'VARIABLE', value: 'my variable'])
+ end
+
+ describe '#parse_tree' do
+ context 'when expression is empty' do
+ let(:text) { '' }
+
+ it 'raises an error' do
+ expect { subject.parse_tree }
+ .to raise_error described_class::StatementError
+ end
+ end
+
+ context 'when expression grammar is incorrect' do
+ table = [
+ '$VAR "text"', # missing operator
+ '== "123"', # invalid right side
+ "'single quotes'", # single quotes string
+ '$VAR ==', # invalid right side
+ '12345', # unknown syntax
+ '' # empty statement
+ ]
+
+ table.each do |syntax|
+ it "raises an error when syntax is `#{syntax}`" do
+ expect { described_class.new(syntax, pipeline).parse_tree }
+ .to raise_error described_class::StatementError
+ end
+ end
+ end
+
+ context 'when expression grammar is correct' do
+ context 'when using an operator' do
+ let(:text) { '$VAR == "value"' }
+
+ it 'returns a reverse descent parse tree' do
+ expect(subject.parse_tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
+ end
+ end
+
+ context 'when using a single token' do
+ let(:text) { '$VARIABLE' }
+
+ it 'returns a single token instance' do
+ expect(subject.parse_tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
+ end
+ end
+ end
+ end
+
+ describe '#evaluate' do
+ statements = [
+ ['$VARIABLE == "my variable"', true],
+ ["$VARIABLE == 'my variable'", true],
+ ['"my variable" == $VARIABLE', true],
+ ['$VARIABLE == null', false],
+ ['$VAR == null', true],
+ ['null == $VAR', true],
+ ['$VARIABLE', 'my variable'],
+ ['$VAR', nil]
+ ]
+
+ statements.each do |expression, value|
+ context "when using expression `#{expression}`" do
+ let(:text) { expression }
+
+ it "evaluates to `#{value.inspect}`" do
+ expect(subject.evaluate).to eq value
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
new file mode 100644
index 00000000000..6d7453f0de5
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Token do
+ let(:value) { '$VARIABLE' }
+ let(:lexeme) { Gitlab::Ci::Pipeline::Expression::Lexeme::Variable }
+
+ subject { described_class.new(value, lexeme) }
+
+ describe '#value' do
+ it 'returns raw token value' do
+ expect(subject.value).to eq value
+ end
+ end
+
+ describe '#lexeme' do
+ it 'returns raw token lexeme' do
+ expect(subject.lexeme).to eq lexeme
+ end
+ end
+
+ describe '#build' do
+ it 'delegates to lexeme after adding a value' do
+ expect(lexeme).to receive(:build)
+ .with(value, 'some', 'args')
+
+ subject.build('some', 'args')
+ end
+
+ it 'allows passing only required arguments' do
+ expect(subject.build).to be_an_instance_of(lexeme)
+ end
+ end
+
+ describe '#type' do
+ it 'delegates type query to the lexeme' do
+ expect(subject.type).to eq :value
+ end
+ end
+
+ describe '#to_lexeme' do
+ it 'returns raw lexeme syntax component name' do
+ expect(subject.to_lexeme).to eq 'variable'
+ end
+ end
+end