summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
blob: d8db9c262a142e7983275baddf62fac51cbd705a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
require 'spec_helper'

describe Gitlab::Ci::Pipeline::Expression::Lexer do
  let(:token_class) do
    Gitlab::Ci::Pipeline::Expression::Token
  end

  describe '#tokens' do
    it 'returns 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 'returns 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 'returns 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 'returns 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

    context 'with complex expressions' do
      using RSpec::Parameterized::TableSyntax

      subject { described_class.new(expression).tokens.map(&:value) }

      where(:expression, :tokens) do
        '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '&&', '$EMPTY_VARIABLE', '=~', '/nope/']
        '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE'                 | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE']
        '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"'       | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE', '!=', '"nope"']
        '$PRESENT_VARIABLE && $EMPTY_VARIABLE'                       | ['$PRESENT_VARIABLE', '&&', '$EMPTY_VARIABLE']
        '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '$EMPTY_VARIABLE', '=~', '/nope/']
        '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE'                 | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE']
        '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE != "nope"'       | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE', '!=', '"nope"']
        '$PRESENT_VARIABLE || $EMPTY_VARIABLE'                       | ['$PRESENT_VARIABLE', '||', '$EMPTY_VARIABLE']
        '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""'         | ['$PRESENT_VARIABLE', '&&', 'null', '||', '$EMPTY_VARIABLE', '==', '""']
      end

      with_them do
        it { is_expected.to eq(tokens) }
      end
    end

    context 'with the ci_variables_complex_expressions feature flag turned off' do
      before do
        stub_feature_flags(ci_variables_complex_expressions: false)
      end

      it 'incorrectly tokenizes conjunctive match statements as one match statement' do
        tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens

        expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
      end

      it 'incorrectly tokenizes disjunctive match statements as one statement' do
        tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens

        expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
      end

      it 'raises an error about && operators' do
        expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
          .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
      end

      it 'raises an error about || operators' do
        expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
          .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
      end
    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