puppet 3.8.1 → 3.8.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (50) hide show
  1. data/ext/build_defaults.yaml +4 -4
  2. data/ext/systemd/puppet.service +1 -1
  3. data/ext/systemd/puppetmaster.service +1 -1
  4. data/lib/hiera/puppet_function.rb +5 -5
  5. data/lib/puppet/defaults.rb +1 -5
  6. data/lib/puppet/environments.rb +85 -4
  7. data/lib/puppet/functions/hiera_include.rb +2 -2
  8. data/lib/puppet/module_tool/applications/unpacker.rb +1 -1
  9. data/lib/puppet/parser/ast/collexpr.rb +1 -1
  10. data/lib/puppet/parser/ast/pops_bridge.rb +5 -0
  11. data/lib/puppet/parser/scope.rb +43 -13
  12. data/lib/puppet/pops/evaluator/evaluator_impl.rb +5 -1
  13. data/lib/puppet/pops/evaluator/runtime3_converter.rb +1 -1
  14. data/lib/puppet/pops/evaluator/runtime3_support.rb +31 -0
  15. data/lib/puppet/pops/issues.rb +10 -2
  16. data/lib/puppet/pops/model/factory.rb +4 -3
  17. data/lib/puppet/pops/model/model_meta.rb +4 -1
  18. data/lib/puppet/pops/parser/egrammar.ra +4 -0
  19. data/lib/puppet/pops/parser/eparser.rb +1167 -1066
  20. data/lib/puppet/pops/parser/epp_support.rb +4 -2
  21. data/lib/puppet/pops/parser/lexer2.rb +6 -0
  22. data/lib/puppet/pops/patterns.rb +3 -0
  23. data/lib/puppet/pops/types/type_parser.rb +2 -2
  24. data/lib/puppet/pops/validation/checker4_0.rb +9 -1
  25. data/lib/puppet/pops/validation/validator_factory_4_0.rb +3 -1
  26. data/lib/puppet/provider/group/pw.rb +1 -0
  27. data/lib/puppet/provider/service/debian.rb +2 -1
  28. data/lib/puppet/provider/user/pw.rb +1 -0
  29. data/lib/puppet/resource.rb +3 -1
  30. data/lib/puppet/resource/type.rb +4 -2
  31. data/lib/puppet/util.rb +9 -1
  32. data/lib/puppet/util/log.rb +1 -1
  33. data/lib/puppet/version.rb +1 -1
  34. data/spec/integration/parser/compiler_spec.rb +9 -7
  35. data/spec/integration/parser/future_compiler_spec.rb +9 -7
  36. data/spec/integration/parser/scope_spec.rb +37 -0
  37. data/spec/unit/environments_spec.rb +2 -0
  38. data/spec/unit/functions/hiera_spec.rb +2 -2
  39. data/spec/unit/parser/scope_spec.rb +9 -0
  40. data/spec/unit/pops/evaluator/runtime3_converter_spec.rb +19 -0
  41. data/spec/unit/pops/evaluator/variables_spec.rb +1 -1
  42. data/spec/unit/pops/parser/lexer2_spec.rb +48 -3
  43. data/spec/unit/pops/types/type_parser_spec.rb +8 -0
  44. data/spec/unit/pops/validator/validator_spec.rb +28 -0
  45. data/spec/unit/provider/service/debian_spec.rb +11 -3
  46. data/spec/unit/provider/zone/solaris_spec.rb +45 -12
  47. data/spec/unit/util/log_spec.rb +12 -1
  48. data/spec/unit/util_spec.rb +21 -2
  49. metadata +3283 -3273
  50. checksums.yaml +0 -7
@@ -183,7 +183,7 @@ module Puppet::Pops::Parser::EppSupport
183
183
  until scanner.eos?
184
184
  part = @scanner.scan_until(/(<%)|\z/)
185
185
  if @skip_leading
186
- part.gsub!(/^[ \t]*\r?\n?/,'')
186
+ part.sub!(/^[ \t]*\r?(?:\n|\z)?/,'')
187
187
  @skip_leading = false
188
188
  end
189
189
  # The spec for %%> is to transform it into a literal %>. This is done here, as %%> otherwise would go
@@ -208,7 +208,7 @@ module Puppet::Pops::Parser::EppSupport
208
208
  # trim trailing whitespace on same line from accumulated s
209
209
  # return text and signal switch to pp mode
210
210
  @scanner.getch # drop the -
211
- s.gsub!(/\r?\n?[ \t]*<%\z/, '')
211
+ s.sub!(/[ \t]*<%\z/, '')
212
212
  @mode = :epp
213
213
  return s
214
214
 
@@ -240,6 +240,8 @@ module Puppet::Pops::Parser::EppSupport
240
240
  @mode = :error
241
241
  return s
242
242
  end
243
+ # Always trim leading whitespace on the same line when there is a comment
244
+ s.sub!(/[ \t]*\z/, '')
243
245
  @skip_leading = true if part.end_with?("-%>")
244
246
  # Continue scanning for more text
245
247
 
@@ -131,7 +131,13 @@ class Puppet::Pops::Parser::Lexer2
131
131
  "type" => [:TYPE, 'type', 4],
132
132
  "attr" => [:ATTR, 'attr', 4],
133
133
  "private" => [:PRIVATE, 'private', 7],
134
+ # The following tokens exist in reserved form. Later they will be made
135
+ # live subject to a feature switch.
136
+ "application" => [:APPLICATION_R, 'application', 11],
137
+ "consumes" => [:CONSUMES_R, 'consumes', 8],
138
+ "produces" => [:PRODUCES_R, 'produces', 8],
134
139
  }
140
+
135
141
  KEYWORDS.each {|k,v| v[1].freeze; v.freeze }
136
142
  KEYWORDS.freeze
137
143
 
@@ -38,6 +38,9 @@ module Puppet::Pops::Patterns
38
38
  # Note, that only the final segment may start with an underscore.
39
39
  VAR_NAME = %r{\A(:?(::)?[a-z]\w*)*(:?(::)?[a-z_]\w*)\z}
40
40
 
41
+ # PARAM_NAME matches the name part of a parameter (The $ character is not included)
42
+ PARAM_NAME = %r{\A[a-z_]\w*\z}
43
+
41
44
  # A Numeric var name must be the decimal number 0, or a decimal number not starting with 0
42
45
  NUMERIC_VAR_NAME = %r{\A(?:0|(?:[1-9][0-9]*))\z}
43
46
 
@@ -356,7 +356,7 @@ class Puppet::Pops::Types::TypeParser
356
356
  if parameters.size == 1
357
357
  case parameters[0]
358
358
  when Integer
359
- TYPES.range(parameters[0], parameters[0])
359
+ TYPES.range(parameters[0], :default)
360
360
  when :default
361
361
  TYPES.integer # unbound
362
362
  end
@@ -370,7 +370,7 @@ class Puppet::Pops::Types::TypeParser
370
370
  if parameters.size == 1
371
371
  case parameters[0]
372
372
  when Integer, Float
373
- TYPES.float_range(parameters[0], parameters[0])
373
+ TYPES.float_range(parameters[0], :default)
374
374
  when :default
375
375
  TYPES.float # unbound
376
376
  end
@@ -427,6 +427,10 @@ class Puppet::Pops::Validation::Checker4_0
427
427
  if o.name =~ /^(?:0x)?[0-9]+$/
428
428
  acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name)
429
429
  end
430
+
431
+ unless o.name =~ Puppet::Pops::Patterns::PARAM_NAME
432
+ acceptor.accept(Issues::ILLEGAL_PARAM_NAME, o, :name => o.name)
433
+ end
430
434
  end
431
435
 
432
436
  #relationship_side: resource
@@ -476,7 +480,11 @@ class Puppet::Pops::Validation::Checker4_0
476
480
  end
477
481
 
478
482
  def check_ReservedWord(o)
479
- acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word)
483
+ if o.future
484
+ acceptor.accept(Issues::FUTURE_RESERVED_WORD, o, :word => o.word)
485
+ else
486
+ acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word)
487
+ end
480
488
  end
481
489
 
482
490
  def check_SelectorExpression(o)
@@ -23,7 +23,9 @@ class Puppet::Pops::Validation::ValidatorFactory_4_0 < Puppet::Pops::Validation:
23
23
  p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning
24
24
  p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning
25
25
 
26
- p[Issues::NAME_WITH_HYPHEN] = :error
26
+ p[Issues::FUTURE_RESERVED_WORD] = :deprecation
27
+
28
+ p[Issues::NAME_WITH_HYPHEN] = :error
27
29
  p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore
28
30
  p
29
31
  end
@@ -7,6 +7,7 @@ Puppet::Type.type(:group).provide :pw, :parent => Puppet::Provider::NameService:
7
7
  has_features :manages_members
8
8
 
9
9
  defaultfor :operatingsystem => [:freebsd, :dragonfly]
10
+ confine :operatingsystem => [:freebsd, :dragonfly]
10
11
 
11
12
  options :members, :flag => "-M", :method => :mem
12
13
 
@@ -38,7 +38,8 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do
38
38
  # See x-man-page://invoke-rc.d
39
39
  if [104, 106].include?($CHILD_STATUS.exitstatus)
40
40
  return :true
41
- elsif [105].include?($CHILD_STATUS.exitstatus)
41
+ elsif [101, 105].include?($CHILD_STATUS.exitstatus)
42
+ # 101 is action not allowed, which means we have to do the check manually.
42
43
  # 105 is unknown, which generally means the iniscript does not support query
43
44
  # The debian policy states that the initscript should support methods of query
44
45
  # For those that do not, peform the checks manually
@@ -8,6 +8,7 @@ Puppet::Type.type(:user).provide :pw, :parent => Puppet::Provider::NameService::
8
8
  has_features :manages_homedir, :allows_duplicates, :manages_passwords, :manages_expiry, :manages_shell
9
9
 
10
10
  defaultfor :operatingsystem => [:freebsd, :dragonfly]
11
+ confine :operatingsystem => [:freebsd, :dragonfly]
11
12
 
12
13
  options :home, :flag => "-d", :method => :dir
13
14
  options :comment, :method => :gecos
@@ -430,6 +430,8 @@ class Puppet::Resource
430
430
  result.environment = environment
431
431
  result.instance_variable_set(:@rstype, resource_type)
432
432
 
433
+ future_parser_not_in_use = !Puppet.future_parser?(result.environment)
434
+
433
435
  to_hash.each do |p, v|
434
436
  if v.is_a?(Puppet::Resource)
435
437
  v = Puppet::Resource.new(v.type, v.title)
@@ -442,7 +444,7 @@ class Puppet::Resource
442
444
  end
443
445
  end
444
446
 
445
- if !Puppet.future_parser?
447
+ if future_parser_not_in_use # !Puppet.future_parser?
446
448
  # If the value is an array with only one value, then
447
449
  # convert it to a single value. This is largely so that
448
450
  # the database interaction doesn't have to worry about
@@ -82,8 +82,10 @@ class Puppet::Resource::Type
82
82
 
83
83
  # External documentation uses "parameters" but the internal name
84
84
  # is "arguments"
85
- data['parameters'] = arguments.dup unless arguments.empty?
86
-
85
+ # Dump any arguments as source
86
+ data['parameters'] = Hash[arguments.map do |k,v|
87
+ [k, v.respond_to?(:source_text) ? v.source_text : v]
88
+ end]
87
89
  data['name'] = name
88
90
 
89
91
  unless RESOURCE_KINDS_TO_EXTERNAL_NAMES.has_key?(type)
@@ -292,7 +292,15 @@ module Util
292
292
  $stdout.reopen(stdout)
293
293
  $stderr.reopen(stderr)
294
294
 
295
- 3.upto(256){|fd| IO::new(fd).close rescue nil}
295
+ begin
296
+ Dir.foreach('/proc/self/fd') do |f|
297
+ if f != '.' && f != '..' && f.to_i >= 3
298
+ IO::new(f.to_i).close rescue nil
299
+ end
300
+ end
301
+ rescue Errno::ENOENT # /proc/self/fd not found
302
+ 3.upto(256){|fd| IO::new(fd).close rescue nil}
303
+ end
296
304
 
297
305
  block.call if block
298
306
  end
@@ -336,7 +336,7 @@ class Puppet::Util::Log
336
336
  # If they pass a source in to us, we make sure it is a string, and
337
337
  # we retrieve any tags we can.
338
338
  def source=(source)
339
- if source.respond_to?(:path)
339
+ if defined?(Puppet::Type) && source.is_a?(Puppet::Type)
340
340
  @source = source.path
341
341
  source.tags.each { |t| tag(t) }
342
342
  self.file = source.file
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
  module Puppet
10
- PUPPETVERSION = '3.8.1'
10
+ PUPPETVERSION = '3.8.2'
11
11
 
12
12
  ##
13
13
  # version is a public API method intended to always provide a fast and
@@ -121,15 +121,17 @@ describe "Puppet::Parser::Compiler" do
121
121
  end
122
122
  end
123
123
 
124
- it "should recompute the version after input files are re-parsed" do
124
+ it 'should recompute the version after input files are re-parsed' do
125
125
  Puppet[:code] = 'class foo { }'
126
- Time.stubs(:now).returns(1)
126
+ first_time = Time.at(1)
127
+ second_time = Time.at(200)
128
+ Time.stubs(:now).returns(first_time)
127
129
  node = Puppet::Node.new('mynode')
128
- Puppet::Parser::Compiler.compile(node).version.should == 1
129
- Time.stubs(:now).returns(2)
130
- Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
131
- Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
132
- Puppet::Parser::Compiler.compile(node).version.should == 2
130
+ expect(Puppet::Parser::Compiler.compile(node).version).to eq(first_time.to_i)
131
+ Time.stubs(:now).returns(second_time)
132
+ expect(Puppet::Parser::Compiler.compile(node).version).to eq(first_time.to_i) # no change because files didn't change
133
+ Puppet[:code] = nil
134
+ expect(Puppet::Parser::Compiler.compile(node).version).to eq(second_time.to_i)
133
135
  end
134
136
 
135
137
  ['class', 'define', 'node'].each do |thing|
@@ -185,15 +185,17 @@ describe "Puppet::Parser::Compiler" do
185
185
  end
186
186
  end
187
187
 
188
- it "should recompute the version after input files are re-parsed" do
188
+ it 'should recompute the version after input files are re-parsed' do
189
189
  Puppet[:code] = 'class foo { }'
190
- Time.stubs(:now).returns(1)
190
+ first_time = Time.at(1)
191
+ second_time = Time.at(200)
192
+ Time.stubs(:now).returns(first_time)
191
193
  node = Puppet::Node.new('mynode')
192
- Puppet::Parser::Compiler.compile(node).version.should == 1
193
- Time.stubs(:now).returns(2)
194
- Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
195
- Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
196
- Puppet::Parser::Compiler.compile(node).version.should == 2
194
+ expect(Puppet::Parser::Compiler.compile(node).version).to eq(first_time.to_i)
195
+ Time.stubs(:now).returns(second_time)
196
+ expect(Puppet::Parser::Compiler.compile(node).version).to eq(first_time.to_i) # no change because files didn't change
197
+ Puppet[:code] = nil
198
+ expect(Puppet::Parser::Compiler.compile(node).version).to eq(second_time.to_i)
197
199
  end
198
200
 
199
201
  ['define', 'class', 'node'].each do |thing|
@@ -32,6 +32,25 @@ describe "Two step scoping for variables" do
32
32
  end
33
33
  end
34
34
  end
35
+
36
+ describe 'handles 3.x/4.x functions' do
37
+ it 'can call a 3.x function via call_function' do
38
+ expect_the_message_to_be('yes') do <<-MANIFEST
39
+ $msg = inline_template('<%= scope().call_function("fqdn_rand", [30]).to_i <= 30 ? "yes" : "no" %>')
40
+ notify { 'something': message => $msg }
41
+ MANIFEST
42
+ end
43
+ end
44
+
45
+ it 'can cannot call a 4.x function via call_function' do
46
+ expect do
47
+ catalog = compile_to_catalog(<<-MANIFEST)
48
+ $msg = inline_template('<%= scope().call_function("with", ["yes"]) { |x| x } %>')
49
+ notify { 'something': message => $msg }
50
+ MANIFEST
51
+ end.to raise_error
52
+ end
53
+ end
35
54
  end
36
55
 
37
56
  context 'using future parser' do
@@ -120,6 +139,24 @@ describe "Two step scoping for variables" do
120
139
  MANIFEST
121
140
  end
122
141
  end
142
+
143
+ describe 'handles 3.x/4.x functions' do
144
+ it 'can call a 3.x function via call_function' do
145
+ expect_the_message_to_be('yes') do <<-MANIFEST
146
+ $msg = inline_template('<%= scope().call_function("fqdn_rand", [30]).to_i <= 30 ? "yes" : "no" %>')
147
+ notify { 'something': message => $msg }
148
+ MANIFEST
149
+ end
150
+ end
151
+
152
+ it 'it can call a 4.x function via call_function' do
153
+ expect_the_message_to_be('yes') do <<-MANIFEST
154
+ $msg = inline_template('<%= scope().call_function("with", ["yes"]) { |x| x } %>')
155
+ notify { 'something': message => $msg }
156
+ MANIFEST
157
+ end
158
+ end
159
+ end
123
160
  end
124
161
 
125
162
  shared_examples_for "the scope" do
@@ -642,6 +642,8 @@ config_version=$vardir/random/scripts
642
642
  end
643
643
 
644
644
  def expired?(env_name)
645
+ # make expired? idempotent
646
+ return true if @expired_envs.include? (env_name)
645
647
  @expired_envs << env_name
646
648
  @expiration_sequence.pop
647
649
  end
@@ -112,7 +112,7 @@ describe 'when calling' do
112
112
 
113
113
  it 'should use the array resolution_type' do
114
114
  Hiera.any_instance.expects(:lookup).with { |*args| args[4].should be(:array) }.returns(%w[foo bar baz])
115
- hiera_include.expects(:call_function).with('include', %w[foo bar baz])
115
+ hiera_include.expects(:call_function_with_scope).with(scope, 'include', %w[foo bar baz])
116
116
  hiera_include.call(scope, 'key', {'key' => 'foo_result'})
117
117
  end
118
118
 
@@ -122,7 +122,7 @@ describe 'when calling' do
122
122
  end
123
123
 
124
124
  it 'should use default block' do
125
- hiera_include.expects(:call_function).with('include', %w[key foo])
125
+ hiera_include.expects(:call_function_with_scope).with(scope,'include', %w[key foo])
126
126
  hiera_include.call(scope, 'foo') { |k| ['key', k] }
127
127
  end
128
128
  end
@@ -33,6 +33,15 @@ describe Puppet::Parser::Scope do
33
33
  end
34
34
  end
35
35
 
36
+ it "should generate a simple string when inspecting a scope" do
37
+ @scope.inspect.should eq("Scope()")
38
+ end
39
+
40
+ it "should generate a simple string when inspecting a scope with a resource" do
41
+ @scope.resource="foo::bar"
42
+ @scope.inspect.should eq("Scope(foo::bar)")
43
+ end
44
+
36
45
  it "should return a scope for use in a test harness" do
37
46
  create_test_scope_for_node("node_name_foo").should be_a_kind_of(Puppet::Parser::Scope)
38
47
  end
@@ -0,0 +1,19 @@
1
+ #! /usr/bin/env ruby
2
+ require 'spec_helper'
3
+
4
+ require 'puppet/pops'
5
+ require 'puppet/pops/types/type_factory'
6
+
7
+ describe 'when converting to 3.x' do
8
+ it "converts a resource type starting with Class without confusing it with exact match on 'class'" do
9
+ t = Puppet::Pops::Types::TypeFactory.resource('classroom', 'kermit')
10
+ converted = Puppet::Pops::Evaluator::Runtime3Converter.instance.catalog_type_to_split_type_title(t)
11
+ expect(converted).to eql(['classroom', 'kermit'])
12
+ end
13
+
14
+ it "converts a resource type of exactly 'Class'" do
15
+ t = Puppet::Pops::Types::TypeFactory.resource('class', 'kermit')
16
+ converted = Puppet::Pops::Evaluator::Runtime3Converter.instance.catalog_type_to_split_type_title(t)
17
+ expect(converted).to eql(['class', 'kermit'])
18
+ end
19
+ end
@@ -41,7 +41,7 @@ describe 'Puppet::Pops::Impl::EvaluatorImpl' do
41
41
 
42
42
  it "access to global names works in local scope" do
43
43
  top_scope_block = block( var('a').set(literal(2)+literal(2)))
44
- local_scope_block = block( var('a').set(var('::a')+literal(2)), var('::a'))
44
+ local_scope_block = block( var('a').set(literal(100)), var('b').set(var('::a')+literal(2)), var('b'))
45
45
  evaluate_l(top_scope_block, local_scope_block).should == 6
46
46
  end
47
47
 
@@ -91,12 +91,25 @@ describe 'Lexer2' do
91
91
  "true" => :BOOLEAN,
92
92
  "in" => :IN,
93
93
  "unless" => :UNLESS,
94
+ "private" => :PRIVATE,
95
+ "type" => :TYPE,
96
+ "attr" => :ATTR,
94
97
  }.each do |string, name|
95
98
  it "should lex a keyword from '#{string}'" do
96
99
  tokens_scanned_from(string).should match_tokens2(name)
97
100
  end
98
101
  end
99
102
 
103
+ {
104
+ "application" => :APPLICATION_R,
105
+ "consumes" => :CONSUMES_R,
106
+ "produces" => :PRODUCES_R,
107
+ }.each do |string, name|
108
+ it "should lex a (future reserved) keyword from '#{string}'" do
109
+ tokens_scanned_from(string).should match_tokens2(name)
110
+ end
111
+ end
112
+
100
113
  # TODO: Complete with all edge cases
101
114
  [ 'A', 'A::B', '::A', '::A::B',].each do |string|
102
115
  it "should lex a CLASSREF on the form '#{string}'" do
@@ -535,7 +548,7 @@ describe 'Lexer2' do
535
548
  [:VARIABLE, "x"],
536
549
  :EQUALS,
537
550
  [:NUMBER, "10"],
538
- [:RENDER_STRING, "just text\n"]
551
+ [:RENDER_STRING, " just text\n"]
539
552
  )
540
553
  end
541
554
 
@@ -551,7 +564,39 @@ describe 'Lexer2' do
551
564
  [:VARIABLE, "x"],
552
565
  :EQUALS,
553
566
  [:NUMBER, "10"],
554
- [:RENDER_STRING, "just text\n"]
567
+ [:RENDER_STRING, " just text\n"]
568
+ )
569
+ end
570
+
571
+ it 'epp comments strips left whitespace when preceding is right trim' do
572
+ code = <<-CODE
573
+ This is <% $x=10 -%>
574
+ space-before-me-but-not-after <%# This is an epp comment %>
575
+ just text
576
+ CODE
577
+ expect(epp_tokens_scanned_from(code)).to match_tokens2(
578
+ :EPP_START,
579
+ [:RENDER_STRING, " This is "],
580
+ [:VARIABLE, "x"],
581
+ :EQUALS,
582
+ [:NUMBER, "10"],
583
+ [:RENDER_STRING, " space-before-me-but-not-after\n just text\n"]
584
+ )
585
+ end
586
+
587
+ it 'epp comments strips left whitespace on same line when preceding is not right trim' do
588
+ code = <<-CODE
589
+ This is <% $x=10 %>
590
+ <%# This is an epp comment -%>
591
+ just text
592
+ CODE
593
+ expect(epp_tokens_scanned_from(code)).to match_tokens2(
594
+ :EPP_START,
595
+ [:RENDER_STRING, " This is "],
596
+ [:VARIABLE, "x"],
597
+ :EQUALS,
598
+ [:NUMBER, "10"],
599
+ [:RENDER_STRING, "\n just text\n"]
555
600
  )
556
601
  end
557
602
 
@@ -566,7 +611,7 @@ describe 'Lexer2' do
566
611
  [:VARIABLE, "x"],
567
612
  :EQUALS,
568
613
  [:NUMBER, "10"],
569
- [:RENDER_STRING, "<% this is escaped epp %>\n"]
614
+ [:RENDER_STRING, " <% this is escaped epp %>\n"]
570
615
  )
571
616
  end
572
617