~ubuntu-branches/ubuntu/oneiric/puppet/oneiric-security

« back to all changes in this revision

Viewing changes to test/ral/providers/cron/crontab.rb

  • Committer: Bazaar Package Importer
  • Author(s): Micah Anderson
  • Date: 2008-07-26 15:43:45 UTC
  • mto: (3.1.1 lenny) (1.3.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20080726154345-1fmgo76b4l72ulvc
ImportĀ upstreamĀ versionĀ 0.24.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env ruby
 
2
 
 
3
require File.dirname(__FILE__) + '/../../../lib/puppettest'
 
4
 
 
5
require 'puppettest'
 
6
require 'mocha'
 
7
require 'puppettest/fileparsing'
 
8
 
 
9
class TestCronParsedProvider < Test::Unit::TestCase
 
10
        include PuppetTest
 
11
        include PuppetTest::FileParsing
 
12
 
 
13
 
 
14
    FIELDS = {
 
15
        :crontab => %w{command minute hour month monthday weekday}.collect { |o| o.intern },
 
16
        :freebsd_special => %w{special command}.collect { |o| o.intern },
 
17
        :environment => [:line],
 
18
        :blank => [:line],
 
19
        :comment => [:line],
 
20
    }
 
21
 
 
22
    # These are potentially multi-line records; there's no one-to-one map, but they model
 
23
    # a full cron job.  These tests assume individual record types will always be correctly
 
24
    # parsed, so all they 
 
25
    def sample_crons
 
26
        unless defined? @sample_crons
 
27
            @sample_crons = YAML.load(File.read(File.join(@crondir, "crontab_collections.yaml")))
 
28
        end
 
29
        @sample_crons
 
30
    end
 
31
 
 
32
    # These are simple lines that can appear in the files; there is a one to one
 
33
    # mapping between records and lines.  We have plenty of redundancy here because
 
34
    # we use these records to build up our complex, multi-line cron jobs below.
 
35
    def sample_records
 
36
        unless defined? @sample_records
 
37
            @sample_records = YAML.load(File.read(File.join(@crondir, "crontab_sample_records.yaml")))
 
38
        end
 
39
        @sample_records
 
40
    end
 
41
 
 
42
    def setup
 
43
        super
 
44
        @type = Puppet::Type.type(:cron)
 
45
        @provider = @type.provider(:crontab)
 
46
        @provider.initvars
 
47
        @crondir = datadir(File.join(%w{providers cron}))
 
48
 
 
49
        @oldfiletype = @provider.filetype
 
50
    end
 
51
 
 
52
    def teardown
 
53
        Puppet::Util::FileType.filetype(:ram).clear
 
54
        @provider.clear
 
55
        super
 
56
    end
 
57
 
 
58
    # Make sure a cron job matches up.  Any non-passed fields are considered absent.
 
59
    def assert_cron_equal(msg, cron, options)
 
60
        assert_instance_of(@provider, cron, "not an instance of provider in %s" % msg)
 
61
        options.each do |param, value|
 
62
            assert_equal(value, cron.send(param), "%s was not equal in %s" % [param, msg])
 
63
        end
 
64
        %w{command environment minute hour month monthday weekday}.each do |var|
 
65
            unless options.include?(var.intern)
 
66
                assert_equal(:absent, cron.send(var), "%s was not parsed absent in %s" % [var, msg])
 
67
            end
 
68
        end
 
69
    end
 
70
 
 
71
    # Make sure a cron record matches.  This only works for crontab records.
 
72
    def assert_record_equal(msg, record, options)
 
73
        unless options.include?(:record_type)
 
74
            raise ArgumentError, "You must pass the required record type"
 
75
        end
 
76
        assert_instance_of(Hash, record, "not an instance of a hash in %s" % msg)
 
77
        options.each do |param, value|
 
78
            assert_equal(value, record[param], "%s was not equal in %s" % [param, msg])
 
79
        end
 
80
        FIELDS[record[:record_type]].each do |var|
 
81
            unless options.include?(var)
 
82
                assert_equal(:absent, record[var], "%s was not parsed absent in %s" % [var, msg])
 
83
            end
 
84
        end
 
85
    end
 
86
 
 
87
    def assert_header(file)
 
88
        header = []
 
89
        file.gsub! /^(# HEADER: .+$)\n/ do
 
90
            header << $1
 
91
            ''
 
92
        end
 
93
        assert_equal(4, header.length, "Did not get four header lines")
 
94
    end
 
95
 
 
96
    # This handles parsing every possible iteration of cron records.  Note that this is only
 
97
    # single-line stuff and doesn't include multi-line values (e.g., with names and/or envs).
 
98
    # Those have separate tests.
 
99
    def test_parse_line
 
100
        # First just do each sample record one by one
 
101
        sample_records.each do |name, options|
 
102
            result = nil
 
103
            assert_nothing_raised("Could not parse %s: '%s'" % [name, options[:text]]) do
 
104
                result = @provider.parse_line(options[:text])
 
105
            end
 
106
            assert_record_equal("record for %s" % name, result, options[:record])
 
107
        end
 
108
 
 
109
        # Then do them all at once.
 
110
        records = []
 
111
        text = ""
 
112
        sample_records.each do |name, options|
 
113
            records << options[:record]
 
114
            text += options[:text] + "\n"
 
115
        end
 
116
 
 
117
        result = nil
 
118
        assert_nothing_raised("Could not match all records in one file") do
 
119
            result = @provider.parse(text)
 
120
        end
 
121
 
 
122
        records.zip(result).each do |should, record|
 
123
            assert_record_equal("record for %s in full match" % should.inspect, record, should)
 
124
        end
 
125
    end
 
126
 
 
127
    # Here we test that each record generates to the correct text.
 
128
    def test_generate_line
 
129
        # First just do each sample record one by one
 
130
        sample_records.each do |name, options|
 
131
            result = nil
 
132
            assert_nothing_raised("Could not generate %s: '%s'" % [name, options[:record]]) do
 
133
                result = @provider.to_line(options[:record])
 
134
            end
 
135
            assert_equal(options[:text], result, "Did not generate correct text for %s" % name)
 
136
        end
 
137
 
 
138
        # Then do them all at once.
 
139
        records = []
 
140
        text = ""
 
141
        sample_records.each do |name, options|
 
142
            records << options[:record]
 
143
            text += options[:text] + "\n"
 
144
        end
 
145
 
 
146
        result = nil
 
147
        assert_nothing_raised("Could not match all records in one file") do
 
148
            result = @provider.to_file(records)
 
149
        end
 
150
 
 
151
        assert_header(result)
 
152
 
 
153
        assert_equal(text, result, "Did not generate correct full crontab")
 
154
    end
 
155
 
 
156
    # Test cronjobs that are made up from multiple records.
 
157
    def test_multi_line_cronjobs
 
158
        fulltext = ""
 
159
        all_records = []
 
160
        sample_crons.each do |name, record_names|
 
161
            records = record_names.collect do |record_name|
 
162
                unless record = sample_records[record_name]
 
163
                    raise "Could not find sample record %s" % record_name
 
164
                end
 
165
                record
 
166
            end
 
167
 
 
168
            text = records.collect { |r| r[:text] }.join("\n") + "\n"
 
169
            record_list = records.collect { |r| r[:record] }
 
170
 
 
171
            # Add it to our full collection
 
172
            all_records += record_list
 
173
            fulltext += text
 
174
 
 
175
            # First make sure we generate each one correctly
 
176
            result = nil
 
177
            assert_nothing_raised("Could not generate multi-line cronjob %s" % [name]) do
 
178
                result = @provider.to_file(record_list)
 
179
            end
 
180
            assert_header(result)
 
181
            assert_equal(text, result, "Did not generate correct text for multi-line cronjob %s" % name)
 
182
 
 
183
            # Now make sure we parse each one correctly
 
184
            assert_nothing_raised("Could not parse multi-line cronjob %s" % [name]) do
 
185
                result = @provider.parse(text)
 
186
            end
 
187
            record_list.zip(result).each do |should, record|
 
188
                assert_record_equal("multiline cronjob %s" % name, record, should)
 
189
            end
 
190
        end
 
191
 
 
192
        # Make sure we can generate it all correctly
 
193
        result = nil
 
194
        assert_nothing_raised("Could not generate all multi-line cronjobs") do
 
195
            result = @provider.to_file(all_records)
 
196
        end
 
197
        assert_header(result)
 
198
        assert_equal(fulltext, result, "Did not generate correct text for all multi-line cronjobs")
 
199
 
 
200
        # Now make sure we parse them all correctly
 
201
        assert_nothing_raised("Could not parse multi-line cronjobs") do
 
202
            result = @provider.parse(fulltext)
 
203
        end
 
204
        all_records.zip(result).each do |should, record|
 
205
            assert_record_equal("multiline cronjob %s", record, should)
 
206
        end
 
207
    end
 
208
 
 
209
    # Take our sample files, and make sure we can entirely parse them,
 
210
    # then that we can generate them again and we get the same data.
 
211
    def test_parse_and_generate_sample_files
 
212
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
213
        crondir = datadir(File.join(%w{providers cron}))
 
214
        files = Dir.glob("%s/crontab.*" % crondir)
 
215
 
 
216
        setme
 
217
        @provider.default_target = @me
 
218
        target = @provider.target_object(@me)
 
219
        files.each do |file|
 
220
            str = args = nil
 
221
            assert_nothing_raised("could not load %s" % file) do
 
222
                str, args = YAML.load(File.read(file))
 
223
            end
 
224
            
 
225
            # Stupid old yaml
 
226
            args.each do |hash|
 
227
                hash.each do |param, value|
 
228
                    if param.is_a?(String) and param =~ /^:/
 
229
                        hash.delete(param)
 
230
                        param = param.sub(/^:/,'').intern
 
231
                        hash[param] = value
 
232
                    end
 
233
 
 
234
                    if value.is_a?(String) and value =~ /^:/
 
235
                        value = value.sub(/^:/,'').intern
 
236
                        hash[param] = value
 
237
                    end
 
238
                end
 
239
            end
 
240
            target.write(str)
 
241
            assert_nothing_raised("could not parse %s" % file) do
 
242
                @provider.prefetch
 
243
            end
 
244
            records = @provider.send(:instance_variable_get, "@records")
 
245
 
 
246
            args.zip(records) do |should, sis|
 
247
                # Make the values a bit more equal.
 
248
                should[:target] = @me
 
249
                should[:ensure] = :present
 
250
                #should[:environment] ||= []
 
251
                should[:on_disk] = true
 
252
                is = sis.dup
 
253
                sis.dup.each do |p,v|
 
254
                    is.delete(p) if v == :absent
 
255
                end
 
256
                assert_equal(should, is,
 
257
                    "Did not parse %s correctly" % file)
 
258
            end
 
259
 
 
260
            assert_nothing_raised("could not generate %s" % file) do
 
261
                @provider.flush_target(@me)
 
262
            end
 
263
 
 
264
            assert_equal(str, target.read, "%s changed" % file)
 
265
            @provider.clear
 
266
        end
 
267
    end
 
268
 
 
269
    # A simple test to see if we can load the cron from disk.
 
270
    def test_load
 
271
        setme()
 
272
        records = nil
 
273
        assert_nothing_raised {
 
274
            records = @provider.retrieve(@me)
 
275
        }
 
276
        assert_instance_of(Array, records, "did not get correct response")
 
277
    end
 
278
 
 
279
    # Test that a cron job turns out as expected, by creating one and generating
 
280
    # it directly
 
281
    def test_simple_to_cron
 
282
        # make the cron
 
283
        setme()
 
284
 
 
285
        name = "yaytest"
 
286
        args = {:name => name,
 
287
            :command => "date > /dev/null",
 
288
            :minute => "30",
 
289
            :user => @me,
 
290
            :record_type => :crontab
 
291
        }
 
292
        # generate the text
 
293
        str = nil
 
294
        assert_nothing_raised {
 
295
            str = @provider.to_line(args)
 
296
        }
 
297
 
 
298
        assert_equal("# Puppet Name: #{name}\n30 * * * * date > /dev/null", str,
 
299
            "Cron did not generate correctly")
 
300
    end
 
301
    
 
302
    # Test that comments are correctly retained
 
303
    def test_retain_comments
 
304
        str = "# this is a comment\n#and another comment\n"
 
305
        user = "fakeuser"
 
306
        records = nil
 
307
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
308
        target = @provider.target_object(user)
 
309
        target.write(str)
 
310
        assert_nothing_raised {
 
311
            @provider.prefetch
 
312
        }
 
313
 
 
314
        assert_nothing_raised {
 
315
            newstr = @provider.flush_target(user)
 
316
            assert(target.read.include?(str), "Comments were lost")
 
317
        }
 
318
    end
 
319
 
 
320
    def test_simpleparsing
 
321
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
322
        text = "5 1,2 * 1 0 /bin/echo funtest"
 
323
 
 
324
        records = nil
 
325
        assert_nothing_raised {
 
326
            records = @provider.parse(text)
 
327
        }
 
328
 
 
329
        should = {
 
330
            :minute => %w{5},
 
331
            :hour => %w{1 2},
 
332
            :monthday => :absent,
 
333
            :month => %w{1},
 
334
            :weekday => %w{0},
 
335
            :command => "/bin/echo funtest"
 
336
        }
 
337
 
 
338
        is = records.shift
 
339
        assert(is, "Did not get record")
 
340
 
 
341
        should.each do |p, v|
 
342
            assert_equal(v, is[p], "did not parse %s correctly" % p)
 
343
        end
 
344
    end
 
345
 
 
346
    # Make sure we can create a cron in an empty tab.
 
347
    # LAK:FIXME This actually modifies the user's crontab,
 
348
    # which is pretty heinous.
 
349
    def test_mkcron_if_empty
 
350
        setme
 
351
        @provider.filetype = @oldfiletype
 
352
 
 
353
        records = @provider.retrieve(@me)
 
354
 
 
355
        target = @provider.target_object(@me)
 
356
 
 
357
        cleanup do
 
358
            if records.length == 0
 
359
                target.remove
 
360
            else
 
361
                target.write(@provider.to_file(records))
 
362
            end
 
363
        end
 
364
 
 
365
        # Now get rid of it
 
366
        assert_nothing_raised("Could not remove cron tab") do
 
367
            target.remove
 
368
        end
 
369
 
 
370
        @provider.flush :target => @me, :command => "/do/something",
 
371
            :record_type => :crontab
 
372
        created = @provider.retrieve(@me)
 
373
        assert(created.detect { |r| r[:command] == "/do/something" },
 
374
            "Did not create cron tab")
 
375
    end
 
376
 
 
377
    # Make sure we correctly bidirectionally parse things.
 
378
    def test_records_and_strings
 
379
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
380
        setme
 
381
 
 
382
        target = @provider.target_object(@me)
 
383
        
 
384
        [
 
385
            "* * * * * /some/command",
 
386
            "0,30 * * * * /some/command",
 
387
            "0-30 * * * * /some/command",
 
388
            "# Puppet Name: name\n0-30 * * * * /some/command",
 
389
            "# Puppet Name: name\nVAR=VALUE\n0-30 * * * * /some/command",
 
390
            "# Puppet Name: name\nVAR=VALUE\nC=D\n0-30 * * * * /some/command",
 
391
            "0 * * * * /some/command"
 
392
        ].each do |str|
 
393
            @provider.initvars
 
394
            str += "\n"
 
395
            target.write(str)
 
396
            assert_equal(str, target.read,
 
397
                "Did not write correctly")
 
398
            assert_nothing_raised("Could not prefetch with %s" % str.inspect) do
 
399
                @provider.prefetch
 
400
            end
 
401
            assert_nothing_raised("Could not flush with %s" % str.inspect) do
 
402
                @provider.flush_target(@me)
 
403
            end
 
404
 
 
405
            assert_equal(str, target.read,
 
406
                "Changed in read/write")
 
407
 
 
408
            @provider.clear
 
409
        end
 
410
    end
 
411
 
 
412
    # Test that a specified cron job will be matched against an existing job
 
413
    # with no name, as long as all fields match
 
414
    def test_matchcron
 
415
        mecron = "0,30 * * * * date
 
416
 
 
417
        * * * * * funtest
 
418
        # a comment
 
419
        0,30 * * 1 * date
 
420
        "
 
421
 
 
422
        youcron = "0,30 * * * * date
 
423
 
 
424
        * * * * * yaytest
 
425
        # a comment
 
426
        0,30 * * 1 * fooness
 
427
        "
 
428
        setme
 
429
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
430
        you = "you"
 
431
 
 
432
        # Write the same tab to multiple targets
 
433
        @provider.target_object(@me).write(mecron.gsub(/^\s+/, ''))
 
434
        @provider.target_object(you).write(youcron.gsub(/^\s+/, ''))
 
435
 
 
436
        # Now make some crons that should match
 
437
        matchers = [
 
438
            @type.create(
 
439
                :name => "yaycron",
 
440
                :minute => [0, 30],
 
441
                :command => "date",
 
442
                :user => @me
 
443
            ),
 
444
            @type.create(
 
445
                :name => "youtest",
 
446
                :command => "yaytest",
 
447
                :user => you
 
448
            )
 
449
        ]
 
450
 
 
451
        nonmatchers = [
 
452
            @type.create(
 
453
                :name => "footest",
 
454
                :minute => [0, 30],
 
455
                :hour => 1,
 
456
                :command => "fooness",
 
457
                :user => @me # wrong target
 
458
            ),
 
459
            @type.create(
 
460
                :name => "funtest2",
 
461
                :command => "funtest",
 
462
                :user => you # wrong target for this cron
 
463
            )
 
464
        ]
 
465
 
 
466
        # Create another cron so we prefetch two of them
 
467
        @type.create(:name => "testing", :minute => 30, :command => "whatever", :user => "you")
 
468
 
 
469
        assert_nothing_raised("Could not prefetch cron") do
 
470
            @provider.prefetch([matchers, nonmatchers].flatten.inject({}) { |crons, cron| crons[cron.name] = cron; crons })
 
471
        end
 
472
 
 
473
        matchers.each do |cron|
 
474
            assert_equal(:present, cron.provider.ensure, "Cron %s was not matched" % cron.name)
 
475
            if value = cron.value(:minute) and value == "*"
 
476
                value = :absent
 
477
            end
 
478
            assert_equal(value, cron.provider.minute, "Minutes were not retrieved, so cron was not matched")
 
479
            assert_equal(cron.value(:target), cron.provider.target, "Cron %s was matched from the wrong target" % cron.name)
 
480
        end
 
481
 
 
482
        nonmatchers.each do |cron|
 
483
            assert_equal(:absent, cron.provider.ensure, "Cron %s was incorrectly matched" % cron.name)
 
484
        end
 
485
    end
 
486
 
 
487
    def test_data
 
488
        setme
 
489
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
490
        target = @provider.target_object(@me)
 
491
        fakedata("data/providers/cron/examples").each do |file|
 
492
            text = File.read(file)
 
493
            target.write(text)
 
494
 
 
495
            assert_nothing_raised("Could not parse %s" % file) do
 
496
                @provider.prefetch
 
497
            end
 
498
            # mark the provider modified
 
499
            @provider.modified(@me)
 
500
 
 
501
            # and zero the text
 
502
            target.write("")
 
503
 
 
504
            result = nil
 
505
            assert_nothing_raised("Could not generate %s" % file) do
 
506
                @provider.flush_target(@me)
 
507
            end
 
508
 
 
509
            # Ignore whitespace differences, since those don't affect function.
 
510
            modtext = text.gsub(/[ \t]+/, " ")
 
511
            modtarget = target.read.gsub(/[ \t]+/, " ")
 
512
            assert_equal(modtext, modtarget,
 
513
                "File was not rewritten the same")
 
514
 
 
515
            @provider.clear
 
516
        end
 
517
    end
 
518
 
 
519
    # Match freebsd's annoying @daily stuff.
 
520
    def test_match_freebsd_special
 
521
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
522
        setme
 
523
 
 
524
        target = @provider.target_object(@me)
 
525
        
 
526
        [
 
527
            "@daily /some/command",
 
528
            "@daily /some/command more"
 
529
        ].each do |str|
 
530
            @provider.initvars
 
531
            str += "\n"
 
532
            target.write(str)
 
533
            assert_nothing_raised("Could not prefetch with %s" % str.inspect) do
 
534
                @provider.prefetch
 
535
            end
 
536
            records = @provider.send(:instance_variable_get, "@records")
 
537
            records.each do |r|
 
538
                assert_equal(:freebsd_special, r[:record_type],
 
539
                    "Did not create lines as freebsd lines")
 
540
            end
 
541
            assert_nothing_raised("Could not flush with %s" % str.inspect) do
 
542
                @provider.flush_target(@me)
 
543
            end
 
544
 
 
545
            assert_equal(str, target.read,
 
546
                "Changed in read/write")
 
547
 
 
548
            @provider.clear
 
549
        end
 
550
    end
 
551
 
 
552
    # #707
 
553
    def test_write_freebsd_special
 
554
        assert_equal(@provider.to_line(:record_type => :crontab, :ensure => :present, :special => "reboot", :command => "/bin/echo something"), "@reboot /bin/echo something")
 
555
    end
 
556
 
 
557
    def test_prefetch
 
558
        cron = @type.create :command => "/bin/echo yay", :name => "test", :hour => 4
 
559
 
 
560
        assert_nothing_raised("Could not prefetch cron") do
 
561
            cron.provider.class.prefetch("test" => cron)
 
562
        end
 
563
    end
 
564
 
 
565
    # Testing #669.
 
566
    def test_environment_settings
 
567
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
568
        setme
 
569
 
 
570
        target = @provider.target_object(@me)
 
571
 
 
572
        # First with no env settings
 
573
        resource = @type.create :command => "/bin/echo yay", :name => "test", :hour => 4
 
574
        cron = resource.provider
 
575
 
 
576
        cron.ensure = :present
 
577
        cron.command = "/bin/echo yay"
 
578
        cron.hour = %w{4}
 
579
        cron.flush
 
580
 
 
581
        result = target.read
 
582
        assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write cron out correctly")
 
583
 
 
584
        # Now set the env
 
585
        cron.environment = "TEST=foo"
 
586
        cron.flush
 
587
 
 
588
        result = target.read
 
589
        assert_equal("# Puppet Name: test\nTEST=foo\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting")
 
590
 
 
591
        # Modify it
 
592
        cron.environment = ["TEST=foo", "BLAH=yay"]
 
593
        cron.flush
 
594
 
 
595
        result = target.read
 
596
        assert_equal("# Puppet Name: test\nTEST=foo\nBLAH=yay\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting")
 
597
 
 
598
        # And remove it
 
599
        cron.environment = :absent
 
600
        cron.flush
 
601
 
 
602
        result = target.read
 
603
        assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting")
 
604
    end
 
605
 
 
606
    # Testing #1216
 
607
    def test_strange_lines
 
608
        @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
 
609
        text = " 5 \t\t 1,2 * 1 0 /bin/echo funtest"
 
610
 
 
611
        records = nil
 
612
        assert_nothing_raised {
 
613
            records = @provider.parse(text)
 
614
        }
 
615
 
 
616
        should = {
 
617
            :minute => %w{5},
 
618
            :hour => %w{1 2},
 
619
            :monthday => :absent,
 
620
            :month => %w{1},
 
621
            :weekday => %w{0},
 
622
            :command => "/bin/echo funtest"
 
623
        }
 
624
 
 
625
        is = records.shift
 
626
        assert(is, "Did not get record")
 
627
 
 
628
        should.each do |p, v|
 
629
            assert_equal(v, is[p], "did not parse %s correctly" % p)
 
630
        end
 
631
    end
 
632
end