pairing with luke, nagios_command provider skeleton
[vinpup.git] / test / other / transactions.rb
blob79971a28ba67d6aacb662a2e5ac443663d51c1d1
1 #!/usr/bin/env ruby
3 require File.dirname(__FILE__) + '/../lib/puppettest'
5 require 'puppet'
6 require 'puppettest'
7 require 'mocha'
8 require 'puppettest/support/resources'
10 class TestTransactions < Test::Unit::TestCase
11     include PuppetTest::FileTesting
12     include PuppetTest::Support::Resources
13     class Fakeprop <Puppet::Property
14         attr_accessor :path, :is, :should, :name
15         def should_to_s(value)
16             value.to_s
17         end
18         def insync?(foo)
19             true
20         end
21         def info(*args)
22             false
23         end
24     end
25     
26     
27     def mkgenerator(&block)
28         $finished = []
29         cleanup { $finished = nil }
30         
31         # Create a bogus type that generates new instances with shorter
32         type = Puppet::Type.newtype(:generator) do
33             newparam(:name, :namevar => true)
34             def finish
35                 $finished << self.name
36             end
37         end
38         if block
39             type.class_eval(&block)
40         end
41         cleanup do
42             Puppet::Type.rmtype(:generator)
43         end
44         
45         return type
46     end
47     
48     # Create a new type that generates instances with shorter names.
49     def mkreducer(&block)
50         type = mkgenerator() do
51             def eval_generate
52                 ret = []
53                 if title.length > 1
54                     ret << self.class.create(:title => title[0..-2])
55                 else
56                     return nil
57                 end
58                 ret
59             end
60         end
61         
62         if block
63             type.class_eval(&block)
64         end
65         
66         return type
67     end
69     def test_reports
70         path1 = tempfile()
71         path2 = tempfile()
72         objects = []
73         objects << Puppet::Type.newfile(
74             :path => path1,
75             :content => "yayness"
76         )
77         objects << Puppet::Type.newfile(
78             :path => path2,
79             :content => "booness"
80         )
82         trans = assert_events([:file_created, :file_created], *objects)
84         report = nil
86         assert_nothing_raised {
87             report = trans.generate_report
88         }
90         # First test the report logs
91         assert(report.logs.length > 0, "Did not get any report logs")
93         report.logs.each do |obj|
94             assert_instance_of(Puppet::Util::Log, obj)
95         end
97         # Then test the metrics
98         metrics = report.metrics
100         assert(metrics, "Did not get any metrics")
101         assert(metrics.length > 0, "Did not get any metrics")
103         assert(metrics.has_key?("resources"), "Did not get object metrics")
104         assert(metrics.has_key?("changes"), "Did not get change metrics")
106         metrics.each do |name, metric|
107             assert_instance_of(Puppet::Util::Metric, metric)
108         end
109     end
111     def test_prefetch
112         # Create a type just for testing prefetch
113         name = :prefetchtesting
114         $prefetched = false
115         type = Puppet::Type.newtype(name) do
116             newparam(:name) {}
117         end
118         
119         cleanup do
120             Puppet::Type.rmtype(name)
121         end
123         # Now create a provider
124         type.provide(:prefetch) do
125             def self.prefetch(resources)
126                 $prefetched = resources
127             end
128         end
130         # Now create an instance
131         inst = type.create :name => "yay"
132         
133         # Create a transaction
134         trans = Puppet::Transaction.new(mk_catalog(inst))
136         # Make sure prefetch works
137         assert_nothing_raised do
138             trans.prefetch
139         end
141         assert_equal({inst.title => inst}, $prefetched, "type prefetch was not called")
143         # Now make sure it gets called from within evaluate()
144         $prefetched = false
145         assert_nothing_raised do
146             trans.evaluate
147         end
149         assert_equal({inst.title => inst}, $prefetched, "evaluate did not call prefetch")
150     end
152     def test_refreshes_generate_events
153         path = tempfile()
154         firstpath = tempfile()
155         secondpath = tempfile()
156         file = Puppet::Type.newfile(:title => "file", :path => path, :content => "yayness")
157         first = Puppet::Type.newexec(:title => "first",
158                                      :command => "/bin/echo first > #{firstpath}",
159                                      :subscribe => [:file, path],
160                                      :refreshonly => true
161         )
162         second = Puppet::Type.newexec(:title => "second",
163                                      :command => "/bin/echo second > #{secondpath}",
164                                      :subscribe => [:exec, "first"],
165                                      :refreshonly => true
166         )
168         assert_apply(file, first, second)
170         assert(FileTest.exists?(secondpath), "Refresh did not generate an event")
171     end
173     unless %x{groups}.chomp.split(/ /).length > 1
174         $stderr.puts "You must be a member of more than one group to test transactions"
175     else
176     def ingroup(gid)
177         require 'etc'
178         begin
179             group = Etc.getgrgid(gid)
180         rescue => detail
181             puts "Could not retrieve info for group %s: %s" % [gid, detail]
182             return nil
183         end
185         return @groups.include?(group.name)
186     end
188     def setup
189         super
190         @groups = %x{groups}.chomp.split(/ /)
191         unless @groups.length > 1
192             p @groups
193             raise "You must be a member of more than one group to test this"
194         end
195     end
197     def newfile(hash = {})
198         tmpfile = tempfile()
199         File.open(tmpfile, "w") { |f| f.puts rand(100) }
201         # XXX now, because os x apparently somehow allows me to make a file
202         # owned by a group i'm not a member of, i have to verify that
203         # the file i just created is owned by one of my groups
204         # grrr
205         unless ingroup(File.stat(tmpfile).gid)
206             Puppet.info "Somehow created file in non-member group %s; fixing" %
207                 File.stat(tmpfile).gid
209             require 'etc'
210             firstgr = @groups[0]
211             unless firstgr.is_a?(Integer)
212                 str = Etc.getgrnam(firstgr)
213                 firstgr = str.gid
214             end
215             File.chown(nil, firstgr, tmpfile)
216         end
218         hash[:name] = tmpfile
219         assert_nothing_raised() {
220             return Puppet.type(:file).create(hash)
221         }
222     end
224     def newexec(file)
225         assert_nothing_raised() {
226             return Puppet.type(:exec).create(
227                 :name => "touch %s" % file,
228                 :path => "/bin:/usr/bin:/sbin:/usr/sbin",
229                 :returns => 0
230             )
231         }
232     end
234     # modify a file and then roll the modifications back
235     def test_filerollback
236         transaction = nil
237         file = newfile()
239         properties = {}
240         check = [:group,:mode]
241         file[:check] = check
243         assert_nothing_raised() {
244             file.retrieve
245         }
247         assert_nothing_raised() {
248             check.each { |property|
249                 value = file.value(property)
250                 assert(value)
251                 properties[property] = value
252             }
253         }
256         component = mk_catalog("file",file)
257         require 'etc'
258         groupname = Etc.getgrgid(File.stat(file.name).gid).name
259         assert_nothing_raised() {
260             # Find a group that it's not set to
261             group = @groups.find { |group| group != groupname }
262             unless group
263                 raise "Could not find suitable group"
264             end
265             file[:group] = group
267             file[:mode] = "755"
268         }
269         trans = assert_events([:file_changed, :file_changed], component)
270         file.retrieve
272         assert_rollback_events(trans, [:file_changed, :file_changed], "file")
274         assert_nothing_raised() {
275             file.retrieve
276         }
277         properties.each { |property,value|
278             assert_equal(
279                 value, file.value(property), "File %s remained %s" % [property, file.value(property)]
280             )
281         }
282     end
284     # test that services are correctly restarted and that work is done
285     # in the right order
286     def test_refreshing
287         transaction = nil
288         file = newfile()
289         execfile = File.join(tmpdir(), "exectestingness")
290         exec = newexec(execfile)
291         properties = {}
292         check = [:group,:mode]
293         file[:check] = check
294         file[:group] = @groups[0]
296         config = mk_catalog(file)
297         config.apply
299         @@tmpfiles << execfile
301         # 'subscribe' expects an array of arrays
302         exec[:subscribe] = [[file.class.name,file.name]]
303         exec[:refreshonly] = true
305         assert_nothing_raised() {
306             file.retrieve
307             exec.retrieve
308         }
310         check.each { |property|
311             properties[property] = file.value(property)
312         }
313         assert_nothing_raised() {
314             file[:mode] = "755"
315         }
317         # Make a new catalog so the resource relationships get
318         # set up.
319         config = mk_catalog(file, exec)
321         trans = assert_events([:file_changed, :triggered], config)
323         assert(FileTest.exists?(execfile), "Execfile does not exist")
324         File.unlink(execfile)
325         assert_nothing_raised() {
326             file[:group] = @groups[1]
327         }
329         trans = assert_events([:file_changed, :triggered], config)
330         assert(FileTest.exists?(execfile), "Execfile does not exist")
331     end
333     # Verify that one component requiring another causes the contained
334     # resources in the requiring component to get refreshed.
335     def test_refresh_across_two_components
336         transaction = nil
337         file = newfile()
338         execfile = File.join(tmpdir(), "exectestingness2")
339         @@tmpfiles << execfile
340         exec = newexec(execfile)
341         properties = {}
342         check = [:group,:mode]
343         file[:check] = check
344         file[:group] = @groups[0]
345         assert_apply(file)
347         config = Puppet::Node::Catalog.new
348         fcomp = Puppet::Type.type(:component).create(:name => "file")
349         config.add_resource fcomp
350         config.add_resource file
351         config.add_edge!(fcomp, file)
353         ecomp = Puppet::Type.type(:component).create(:name => "exec")
354         config.add_resource ecomp
355         config.add_resource exec
356         config.add_edge!(ecomp, exec)
358         # 'subscribe' expects an array of arrays
359         #component[:require] = [[file.class.name,file.name]]
360         ecomp[:subscribe] = fcomp
361         exec[:refreshonly] = true
363         trans = assert_events([], config)
365         assert_nothing_raised() {
366             file[:group] = @groups[1]
367             file[:mode] = "755"
368         }
370         trans = assert_events([:file_changed, :file_changed, :triggered], config)
371     end
373     # Make sure that multiple subscriptions get triggered.
374     def test_multisubs
375         path = tempfile()
376         file1 = tempfile()
377         file2 = tempfile()
378         file = Puppet.type(:file).create(
379             :path => path,
380             :ensure => "file"
381         )
382         exec1 = Puppet.type(:exec).create(
383             :path => ENV["PATH"],
384             :command => "touch %s" % file1,
385             :refreshonly => true,
386             :subscribe => [:file, path]
387         )
388         exec2 = Puppet.type(:exec).create(
389             :path => ENV["PATH"],
390             :command => "touch %s" % file2,
391             :refreshonly => true,
392             :subscribe => [:file, path]
393         )
395         assert_apply(file, exec1, exec2)
396         assert(FileTest.exists?(file1), "File 1 did not get created")
397         assert(FileTest.exists?(file2), "File 2 did not get created")
398     end
400     # Make sure that a failed trigger doesn't result in other events not
401     # getting triggered.
402     def test_failedrefreshes
403         path = tempfile()
404         newfile = tempfile()
405         file = Puppet.type(:file).create(
406             :path => path,
407             :ensure => "file"
408         )
409         exec1 = Puppet.type(:exec).create(
410             :path => ENV["PATH"],
411             :command => "touch /this/cannot/possibly/exist",
412             :logoutput => true,
413             :refreshonly => true,
414             :subscribe => file,
415             :title => "one"
416         )
417         exec2 = Puppet.type(:exec).create(
418             :path => ENV["PATH"],
419             :command => "touch %s" % newfile,
420             :logoutput => true,
421             :refreshonly => true,
422             :subscribe => [file, exec1],
423             :title => "two"
424         )
426         assert_apply(file, exec1, exec2)
427         assert(FileTest.exists?(newfile), "Refresh file did not get created")
428     end
430     # Make sure that unscheduled and untagged objects still respond to events
431     def test_unscheduled_and_untagged_response
432         Puppet::Type.type(:schedule).mkdefaultschedules
433         Puppet[:ignoreschedules] = false
434         file = Puppet.type(:file).create(
435             :name => tempfile(),
436             :ensure => "file",
437             :backup => false
438         )
440         fname = tempfile()
441         exec = Puppet.type(:exec).create(
442             :name => "touch %s" % fname,
443             :path => "/usr/bin:/bin",
444             :schedule => "monthly",
445             :subscribe => ["file", file.name]
446         )
448         config = mk_catalog(file, exec)
450         # Run it once
451         assert_apply(config)
452         assert(FileTest.exists?(fname), "File did not get created")
454         assert(!exec.scheduled?, "Exec is somehow scheduled")
456         # Now remove it, so it can get created again
457         File.unlink(fname)
459         file[:content] = "some content"
461         assert_events([:file_changed, :triggered], config)
463         assert(FileTest.exists?(fname), "File did not get recreated")
465         # Now remove it, so it can get created again
466         File.unlink(fname)
468         # And tag our exec
469         exec.tag("testrun")
471         # And our file, so it runs
472         file.tag("norun")
474         Puppet[:tags] = "norun"
476         file[:content] = "totally different content"
478         assert(! file.insync?(file.retrieve), "Uh, file is in sync?")
480         assert_events([:file_changed, :triggered], config)
481         assert(FileTest.exists?(fname), "File did not get recreated")
482     end
484     def test_failed_reqs_mean_no_run
485         exec = Puppet::Type.type(:exec).create(
486             :command => "/bin/mkdir /this/path/cannot/possibly/exit",
487             :title => "mkdir"
488         )
490         file1 = Puppet::Type.type(:file).create(
491             :title => "file1",
492             :path => tempfile(),
493             :require => exec,
494             :ensure => :file
495         )
497         file2 = Puppet::Type.type(:file).create(
498             :title => "file2",
499             :path => tempfile(),
500             :require => file1,
501             :ensure => :file
502         )
504         config = mk_catalog(exec, file1, file2)
506         assert_apply(config)
508         assert(! FileTest.exists?(file1[:path]),
509             "File got created even tho its dependency failed")
510         assert(! FileTest.exists?(file2[:path]),
511             "File got created even tho its deep dependency failed")
512     end
513     end
514     
515     def test_relationship_graph
516         config = mktree
518         config.meta_def(:f) do |name|
519             self.resource("File[%s]" % name)
520         end
521         
522         {"one" => "two", "File[f]" => "File[c]", "File[h]" => "middle"}.each do |source_ref, target_ref|
523             source = config.resource(source_ref) or raise "Missing %s" % source_ref
524             target = config.resource(target_ref) or raise "Missing %s" % target_ref
525             target[:require] = source
526         end
527         
528         trans = Puppet::Transaction.new(config)
529         
530         graph = nil
531         assert_nothing_raised do
532             graph = trans.relationship_graph
533         end
535         assert_instance_of(Puppet::Node::Catalog, graph,
536             "Did not get relationship graph")
537         
538         # Make sure all of the components are gone
539         comps = graph.vertices.find_all { |v| v.is_a?(Puppet::Type::Component)}
540         assert(comps.empty?, "Deps graph still contains components %s" %
541             comps.collect { |c| c.ref }.join(","))
542         
543         assert_equal([], comps, "Deps graph still contains components")
544         
545         # It must be reversed because of how topsort works
546         sorted = graph.topsort.reverse
547         
548         # Now make sure the appropriate edges are there and are in the right order
549         assert(graph.dependents(config.f(:f)).include?(config.f(:c)),
550             "c not marked a dep of f")
551         assert(sorted.index(config.f(:c)) < sorted.index(config.f(:f)),
552             "c is not before f")
553             
554         config.resource("one").each do |o|
555             config.resource("two").each do |t|
556                 assert(graph.dependents(o).include?(t),
557                     "%s not marked a dep of %s" % [t.ref, o.ref])
558                 assert(sorted.index(t) < sorted.index(o),
559                     "%s is not before %s" % [t.ref, o.ref])
560             end
561         end
562         
563         trans.catalog.leaves(config.resource("middle")).each do |child|
564             assert(graph.dependents(config.f(:h)).include?(child),
565                 "%s not marked a dep of h" % [child.ref])
566             assert(sorted.index(child) < sorted.index(config.f(:h)),
567                 "%s is not before h" % child.ref)
568         end
569         
570         # Lastly, make sure our 'g' vertex made it into the relationship
571         # graph, since it's not involved in any relationships.
572         assert(graph.vertex?(config.f(:g)),
573             "Lost vertexes with no relations")
575         # Now make the reversal graph and make sure all of the vertices made it into that
576         reverse = graph.reversal
577         %w{a b c d e f g h}.each do |letter|
578             file = config.f(letter)
579             assert(reverse.vertex?(file), "%s did not make it into reversal" % letter)
580         end
581     end
582     
583     # Test pre-evaluation generation
584     def test_generate
585         mkgenerator() do
586             def generate
587                 ret = []
588                 if title.length > 1
589                     ret << self.class.create(:title => title[0..-2])
590                 else
591                     return nil
592                 end
593                 ret
594             end
595         end
596         
597         yay = Puppet::Type.newgenerator :title => "yay"
598         rah = Puppet::Type.newgenerator :title => "rah"
599         config = mk_catalog(yay, rah)
600         trans = Puppet::Transaction.new(config)
601         
602         assert_nothing_raised do
603             trans.generate
604         end
605         
606         %w{ya ra y r}.each do |name|
607             assert(trans.catalog.vertex?(Puppet::Type.type(:generator)[name]),
608                 "Generated %s was not a vertex" % name)
609             assert($finished.include?(name), "%s was not finished" % name)
610         end
611         
612         # Now make sure that cleanup gets rid of those generated types.
613         assert_nothing_raised do
614             trans.cleanup
615         end
616         
617         %w{ya ra y r}.each do |name|
618             assert(!trans.catalog.vertex?(Puppet::Type.type(:generator)[name]),
619                 "Generated vertex %s was not removed from graph" % name)
620             assert_nil(Puppet::Type.type(:generator)[name],
621                 "Generated vertex %s was not removed from class" % name)
622         end
623     end
624     
625     # Test mid-evaluation generation.
626     def test_eval_generate
627         $evaluated = []
628         cleanup { $evaluated = nil }
629         type = mkreducer() do
630             def evaluate
631                 $evaluated << self.title
632                 return []
633             end
634         end
636         yay = Puppet::Type.newgenerator :title => "yay"
637         rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay
638         config = mk_catalog(yay, rah)
639         trans = Puppet::Transaction.new(config)
640         
641         trans.prepare
642         
643         # Now apply the resources, and make sure they appropriately generate
644         # things.
645         assert_nothing_raised("failed to apply yay") do
646             trans.eval_resource(yay)
647         end
648         ya = type["ya"]
649         assert(ya, "Did not generate ya")
650         assert(trans.relationship_graph.vertex?(ya),
651             "Did not add ya to rel_graph")
652         
653         # Now make sure the appropriate relationships were added
654         assert(trans.relationship_graph.edge?(yay, ya),
655             "parent was not required by child")
656         assert(! trans.relationship_graph.edge?(ya, rah),
657             "generated child ya inherited depencency on rah")
658         
659         # Now make sure it in turn eval_generates appropriately
660         assert_nothing_raised("failed to apply yay") do
661             trans.eval_resource(type["ya"])
662         end
664         %w{y}.each do |name|
665             res = type[name]
666             assert(res, "Did not generate %s" % name)
667             assert(trans.relationship_graph.vertex?(res),
668                 "Did not add %s to rel_graph" % name)
669             assert($finished.include?("y"), "y was not finished")
670         end
671         
672         assert_nothing_raised("failed to eval_generate with nil response") do
673             trans.eval_resource(type["y"])
674         end
675         assert(trans.relationship_graph.edge?(yay, ya), "no edge was created for ya => yay")
676         
677         assert_nothing_raised("failed to apply rah") do
678             trans.eval_resource(rah)
679         end
681         ra = type["ra"]
682         assert(ra, "Did not generate ra")
683         assert(trans.relationship_graph.vertex?(ra),
684             "Did not add ra to rel_graph" % name)
685         assert($finished.include?("ra"), "y was not finished")
686         
687         # Now make sure this generated resource has the same relationships as
688         # the generating resource
689         assert(! trans.relationship_graph.edge?(yay, ra),
690            "rah passed its dependencies on to its children")
691         assert(! trans.relationship_graph.edge?(ya, ra),
692             "children have a direct relationship")
693         
694         # Now make sure that cleanup gets rid of those generated types.
695         assert_nothing_raised do
696             trans.cleanup
697         end
698         
699         %w{ya ra y r}.each do |name|
700             assert(!trans.relationship_graph.vertex?(type[name]),
701                 "Generated vertex %s was not removed from graph" % name)
702             assert_nil(type[name],
703                 "Generated vertex %s was not removed from class" % name)
704         end
705         
706         # Now, start over and make sure that everything gets evaluated.
707         trans = Puppet::Transaction.new(config)
708         $evaluated.clear
709         assert_nothing_raised do
710             trans.evaluate
711         end
712         
713         assert_equal(%w{yay ya y rah ra r}, $evaluated,
714             "Not all resources were evaluated or not in the right order")
715     end
717     def test_ignore_tags?
718         config = Puppet::Node::Catalog.new
719         config.host_config = true
720         transaction = Puppet::Transaction.new(config)
721         assert(! transaction.ignore_tags?, "Ignoring tags when applying a host catalog")
723         config.host_config = false
724         transaction = Puppet::Transaction.new(config)
725         assert(transaction.ignore_tags?, "Not ignoring tags when applying a non-host catalog")
726     end
727     
728     def test_missing_tags?
729         resource = stub 'resource', :tagged? => true
730         config = Puppet::Node::Catalog.new
732         # Mark it as a host config so we don't care which test is first
733         config.host_config = true
734         transaction = Puppet::Transaction.new(config)
735         assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when none are set")
737         # host catalogs pay attention to tags, no one else does.
738         Puppet[:tags] = "three,four"
739         config.host_config = false
740         transaction = Puppet::Transaction.new(config)
741         assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when not running a host catalog")
743         # 
744         config.host_config = true
745         transaction = Puppet::Transaction.new(config)
746         assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when running a host catalog and all tags are present")
748         transaction = Puppet::Transaction.new(config)
749         resource.stubs :tagged? => false
750         assert(transaction.missing_tags?(resource), "Considered a resource not to be missing tags when running a host catalog and tags are missing")
751     end
752     
753     # Make sure changes generated by eval_generated resources have proxies
754     # set to the top-level resource.
755     def test_proxy_resources
756         type = mkreducer do
757             def evaluate
758                 return Puppet::PropertyChange.new(Fakeprop.new(
759                     :path => :path, :is => :is, :should => :should, :name => self.name, :resource => "a parent"), :is)
760             end
761         end
762         
763         resource = type.create :name => "test"
764         config = mk_catalog(resource)
765         trans = Puppet::Transaction.new(config)
766         trans.prepare
768         assert_nothing_raised do
769             trans.eval_resource(resource)
770         end
772         changes = trans.instance_variable_get("@changes")
773         
774         assert(changes.length > 0, "did not get any changes")
775         
776         changes.each do |change|
777             assert_equal(resource, change.source, "change did not get proxy set correctly")
778         end
779     end
780     
781     # Make sure changes in contained files still generate callback events.
782     def test_generated_callbacks
783         dir = tempfile()
784         maker = tempfile()
785         Dir.mkdir(dir)
786         file = File.join(dir, "file")
787         File.open(file, "w") { |f| f.puts "" }
788         File.chmod(0644, file)
789         File.chmod(0755, dir) # So only the child file causes a change
790         
791         dirobj = Puppet::Type.type(:file).create :mode => "755", :recurse => true, :path => dir
792         exec = Puppet::Type.type(:exec).create :title => "make",
793             :command => "touch #{maker}", :path => ENV['PATH'], :refreshonly => true,
794             :subscribe => dirobj
795         
796         assert_apply(dirobj, exec)
797         assert(FileTest.exists?(maker), "Did not make callback file")
798     end
800     # Yay, this out to be fun.
801     def test_trigger
802         $triggered = []
803         cleanup { $triggered = nil }
804         trigger = Class.new do
805             attr_accessor :name
806             include Puppet::Util::Logging
807             def initialize(name)
808                 @name = name
809             end
810             def ref
811                 self.name
812             end
813             def refresh
814                 $triggered << self.name
815             end
817             def to_s
818                 self.name
819             end
820         end
822         # Make a graph with some stuff in it.
823         graph = Puppet::Node::Catalog.new
825         # Add a non-triggering edge.
826         a = trigger.new(:a)
827         b = trigger.new(:b)
828         c = trigger.new(:c)
829         nope = Puppet::Relationship.new(a, b)
830         yep = Puppet::Relationship.new(a, c, {:callback => :refresh})
831         graph.add_edge!(nope)
833         # And a triggering one.
834         graph.add_edge!(yep)
836         # Create our transaction
837         trans = Puppet::Transaction.new(graph)
839         # Set the non-triggering on
840         assert_nothing_raised do
841             trans.set_trigger(nope)
842         end
844         assert(! trans.targeted?(b), "b is incorrectly targeted")
846         # Now set the other
847         assert_nothing_raised do
848             trans.set_trigger(yep)
849         end
850         assert(trans.targeted?(c), "c is not targeted")
852         # Now trigger our three resources
853         assert_nothing_raised do
854             assert_nil(trans.trigger(a), "a somehow triggered something")
855         end
856         assert_nothing_raised do
857             assert_nil(trans.trigger(b), "b somehow triggered something")
858         end
859         assert_equal([], $triggered,"got something in triggered")
860         result = nil
861         assert_nothing_raised do
862             result = trans.trigger(c)
863         end
864         assert(result, "c did not trigger anything")
865         assert_instance_of(Array, result)
866         event = result.shift
867         assert_instance_of(Puppet::Event, event)
868         assert_equal(:triggered, event.event, "event was not set correctly")
869         assert_equal(c, event.source, "source was not set correctly")
870         assert_equal(trans, event.transaction, "transaction was not set correctly")
872         assert(trans.triggered?(c, :refresh),
873             "Transaction did not store the trigger")
874     end
875     
876     def test_set_target
877         file = Puppet::Type.newfile(:path => tempfile(), :content => "yay")
878         exec1 = Puppet::Type.type(:exec).create :command => "/bin/echo exec1"
879         exec2 = Puppet::Type.type(:exec).create :command => "/bin/echo exec2"
880         trans = Puppet::Transaction.new(mk_catalog(file, exec1, exec2))
881         
882         # First try it with an edge that has no callback
883         edge = Puppet::Relationship.new(file, exec1)
884         assert_nothing_raised { trans.set_trigger(edge) }
885         assert(! trans.targeted?(exec1), "edge with no callback resulted in a target")
886         
887         # Now with an edge that has an unsupported callback
888         edge = Puppet::Relationship.new(file, exec1, :callback => :nosuchmethod, :event => :ALL_EVENTS)
889         assert_nothing_raised { trans.set_trigger(edge) }
890         assert(! trans.targeted?(exec1), "edge with invalid callback resulted in a target")
891         
892         # Lastly, with an edge with a supported callback
893         edge = Puppet::Relationship.new(file, exec1, :callback => :refresh, :event => :ALL_EVENTS)
894         assert_nothing_raised { trans.set_trigger(edge) }
895         assert(trans.targeted?(exec1), "edge with valid callback did not result in a target")
896     end
897     
898     # Testing #401 -- transactions are calling refresh() on classes that don't support it.
899     def test_callback_availability
900         $called = []
901         klass = Puppet::Type.newtype(:norefresh) do
902             newparam(:name, :namevar => true) {}
903             def method_missing(method, *args)
904                 $called << method
905             end
906         end
907         cleanup do
908             $called = nil
909             Puppet::Type.rmtype(:norefresh)
910         end
912         file = Puppet::Type.newfile :path => tempfile(), :content => "yay"
913         one = klass.create :name => "one", :subscribe => file
914         
915         assert_apply(file, one)
916         
917         assert(! $called.include?(:refresh), "Called refresh when it wasn't set as a method")
918     end
920     # Testing #437 - cyclic graphs should throw failures.
921     def test_fail_on_cycle
922         one = Puppet::Type.type(:exec).create(:name => "/bin/echo one")
923         two = Puppet::Type.type(:exec).create(:name => "/bin/echo two")
924         one[:require] = two
925         two[:require] = one
927         config = mk_catalog(one, two)
928         trans = Puppet::Transaction.new(config)
929         assert_raise(Puppet::Error) do
930             trans.prepare
931         end
932     end
934     def test_errors_during_generation
935         type = Puppet::Type.newtype(:failer) do
936             newparam(:name) {}
937             def eval_generate
938                 raise ArgumentError, "Invalid value"
939             end
940             def generate
941                 raise ArgumentError, "Invalid value"
942             end
943         end
944         cleanup { Puppet::Type.rmtype(:failer) }
946         obj = type.create(:name => "testing")
948         assert_apply(obj)
949     end
950     
951     def test_self_refresh_causes_triggering
952         type = Puppet::Type.newtype(:refresher, :self_refresh => true) do
953             attr_accessor :refreshed, :testing
954             newparam(:name) {}
955             newproperty(:testing) do
956                 def sync
957                     self.is = self.should
958                     :ran_testing
959                 end
960             end
961             def refresh
962                 @refreshed = true
963             end
964         end
965         cleanup { Puppet::Type.rmtype(:refresher)}
966         
967         obj = type.create(:name => "yay", :testing => "cool")
968         
969         assert(! obj.insync?(obj.retrieve), "fake object is already in sync")
970         
971         # Now make sure it gets refreshed when the change happens
972         assert_apply(obj)
973         assert(obj.refreshed, "object was not refreshed during transaction")
974     end
975     
976     # Testing #433
977     def test_explicit_dependencies_beat_automatic
978         # Create a couple of different resource sets that have automatic relationships and make sure the manual relationships win
979         rels = {}
980         # First users and groups
981         group = Puppet::Type.type(:group).create(:name => nonrootgroup.name, :ensure => :present)
982         user = Puppet::Type.type(:user).create(:name => nonrootuser.name, :ensure => :present, :gid => group.title)
983         
984         # Now add the explicit relationship
985         group[:require] = user
986         rels[group] = user
987         # Now files
988         d = tempfile()
989         f = File.join(d, "file")
990         file = Puppet::Type.newfile(:path => f, :content => "yay")
991         dir = Puppet::Type.newfile(:path => d, :ensure => :directory, :require => file)
992         
993         rels[dir] = file
994         rels.each do |after, before|
995             config = mk_catalog(before, after)
996             trans = Puppet::Transaction.new(config)
997             str = "from %s to %s" % [before, after]
998         
999             assert_nothing_raised("Failed to create graph %s" % str) do
1000                 trans.prepare
1001             end
1002         
1003             graph = trans.relationship_graph
1004             assert(graph.edge?(before, after), "did not create manual relationship %s" % str)
1005             assert(! graph.edge?(after, before), "created automatic relationship %s" % str)
1006         end
1007     end
1009     # #542 - make sure resources in noop mode still notify their resources,
1010     # so that users know if a service will get restarted.
1011     def test_noop_with_notify
1012         path = tempfile
1013         epath = tempfile
1014         spath = tempfile
1015         file = Puppet::Type.newfile(:path => path, :ensure => :file,
1016             :title => "file")
1017         exec = Puppet::Type.type(:exec).create(:command => "touch %s" % epath,
1018             :path => ENV["PATH"], :subscribe => file, :refreshonly => true,
1019             :title => 'exec1')
1020         exec2 = Puppet::Type.type(:exec).create(:command => "touch %s" % spath,
1021             :path => ENV["PATH"], :subscribe => exec, :refreshonly => true,
1022             :title => 'exec2')
1024         Puppet[:noop] = true
1026         assert(file.noop, "file not in noop")
1027         assert(exec.noop, "exec not in noop")
1029         @logs.clear
1030         assert_apply(file, exec, exec2)
1032         assert(! FileTest.exists?(path), "Created file in noop")
1033         assert(! FileTest.exists?(epath), "Executed exec in noop")
1034         assert(! FileTest.exists?(spath), "Executed second exec in noop")
1036         assert(@logs.detect { |l|
1037             l.message =~ /should be/  and l.source == file.property(:ensure).path},
1038                 "did not log file change")
1039         assert(@logs.detect { |l|
1040             l.message =~ /Would have/ and l.source == exec.path },
1041                 "did not log first exec trigger")
1042         assert(@logs.detect { |l|
1043             l.message =~ /Would have/ and l.source == exec2.path },
1044                 "did not log second exec trigger")
1045     end
1047     def test_only_stop_purging_with_relations
1048         files = []
1049         paths = []
1050         3.times do |i|
1051             path = tempfile
1052             paths << path
1053             file = Puppet::Type.newfile(:path => path, :ensure => :absent,
1054                 :backup => false, :title => "file%s" % i)
1055             File.open(path, "w") { |f| f.puts "" }
1056             files << file
1057         end
1059         files[0][:ensure] = :file
1060         files[0][:require] = files[1..2]
1062         # Mark the second as purging
1063         files[1].purging
1065         assert_apply(*files)
1067         assert(FileTest.exists?(paths[1]), "Deleted required purging file")
1068         assert(! FileTest.exists?(paths[2]), "Did not delete non-purged file")
1069     end
1071     def test_flush
1072         $state = "absent"
1073         $flushed = 0
1074         type = Puppet::Type.newtype(:flushtest) do
1075             newparam(:name)
1076             newproperty(:ensure) do
1077                 def retrieve
1078                     $state
1079                 end
1080                 def set(value)
1081                     $state = value
1082                     :thing_changed
1083                 end
1084             end
1086             def flush
1087                 $flushed += 1
1088             end
1089         end
1091         cleanup { Puppet::Type.rmtype(:flushtest) }
1093         obj = type.create(:name => "test", :ensure => "present")
1095         # first make sure it runs through and flushes
1096         assert_apply(obj)
1098         assert_equal("present", $state, "Object did not make a change")
1099         assert_equal(1, $flushed, "object was not flushed")
1101         # Now run a noop and make sure we don't flush
1102         obj[:ensure] = "other"
1103         obj[:noop] = true
1105         assert_apply(obj)
1106         assert_equal("present", $state, "Object made a change in noop")
1107         assert_equal(1, $flushed, "object was flushed in noop")
1108     end