├── spec ├── acceptance │ ├── spec │ │ ├── fixtures │ │ │ └── test2 │ │ └── init_spec.pp │ ├── Rakefile │ └── manifests │ │ └── init.pp ├── unit │ └── puppet │ │ ├── util │ │ └── assertion │ │ │ ├── stubs_spec.rb │ │ │ ├── printer_spec.rb │ │ │ └── reporter_spec.rb │ │ ├── parser │ │ └── functions │ │ │ ├── stub_fact_spec.rb │ │ │ ├── stub_class_spec.rb │ │ │ └── stub_type_spec.rb │ │ ├── type │ │ └── assertion_spec.rb │ │ └── application │ │ └── spec_spec.rb └── integration │ └── puppet │ ├── parser │ └── functions │ │ ├── fixture_spec.rb │ │ ├── stub_class_spec.rb │ │ ├── stub_type_spec.rb │ │ └── stub_facts_spec.rb │ ├── util │ └── assertion │ │ ├── printer_spec.rb │ │ └── reporter_spec.rb │ ├── type │ └── assertion_spec.rb │ ├── application │ └── spec_spec.rb │ └── provider │ └── assertion │ └── evaluator_spec.rb ├── .gitignore ├── Gemfile ├── lib ├── puppet-spec │ └── tasks.rb └── puppet │ ├── parser │ └── functions │ │ ├── fixture.rb │ │ ├── stub_facts.rb │ │ ├── stub_class.rb │ │ └── stub_type.rb │ ├── util │ └── assertion │ │ ├── stubs.rb │ │ ├── printer.rb │ │ └── reporter.rb │ ├── provider │ └── assertion │ │ └── evaluator.rb │ ├── type │ └── assertion.rb │ └── application │ └── spec.rb ├── .gemspec ├── CHANGELOG ├── Rakefile ├── metadata.json ├── .travis.yml ├── README.md └── LICENSE /spec/acceptance/spec/fixtures/test2: -------------------------------------------------------------------------------- 1 | stub content 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | pkg/ 3 | puppet-spec*.gem 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /spec/acceptance/Rakefile: -------------------------------------------------------------------------------- 1 | require 'puppet-spec/tasks' 2 | -------------------------------------------------------------------------------- /lib/puppet-spec/tasks.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/spec' 2 | 3 | desc "Evaluate puppet-spec test cases and print the results" 4 | task(:puppetspec) do 5 | Puppet::Application::Spec.new.run 6 | end 7 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/fixture.rb: -------------------------------------------------------------------------------- 1 | Puppet::Parser::Functions.newfunction(:fixture, :arity => 1, :type => :rvalue) do |values| 2 | modulepath = compiler.environment.full_modulepath[0] 3 | file = Dir.glob("#{modulepath}/*/spec/fixtures/#{values[0]}")[0] 4 | 5 | return File.read(file) 6 | end 7 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/stub_facts.rb: -------------------------------------------------------------------------------- 1 | Puppet::Parser::Functions.newfunction(:stub_facts, :arity => 1) do |values| 2 | 3 | raise Puppet::Error, "stub_facts accepts a hash of fact/value pairs" unless values[0].is_a?(Hash) 4 | 5 | values[0].each do |key, value| 6 | compiler.topscope[key] = value 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/puppet/util/assertion/stubs_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/stubs' 2 | 3 | describe Puppet::Util::Assertion::Stubs::Type do 4 | subject { Puppet::Util::Assertion::Stubs::Type.new(:definition, 'stub resource') } 5 | 6 | describe ".valid_parameter?" do 7 | it "should return true" do 8 | expect(subject.valid_parameter?('anything')).to be true 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/stub_class.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/stubs' 2 | 3 | Puppet::Parser::Functions.newfunction(:stub_class, :arity => 1) do |values| 4 | 5 | raise Puppet::Error, "stub_class accepts a class name in the form of a string" unless values[0].is_a?(String) 6 | 7 | compiler.environment.known_resource_types << Puppet::Util::Assertion::Stubs::Type.new(:hostclass, values[0]) 8 | 9 | end 10 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/stub_type.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/stubs' 2 | 3 | Puppet::Parser::Functions.newfunction(:stub_type, :arity => 1) do |values| 4 | 5 | raise Puppet::Error, "stub_type accepts a type name in the form of a string" unless values[0].is_a?(String) 6 | 7 | compiler.environment.known_resource_types << Puppet::Util::Assertion::Stubs::Type.new(:definition, values[0]) 8 | 9 | end 10 | -------------------------------------------------------------------------------- /lib/puppet/util/assertion/stubs.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/resource/type' 2 | 3 | module Puppet::Util::Assertion 4 | class Stubs 5 | 6 | # This type reimplements the Puppet::Resource::Type class 7 | # and overwrites the parameter validation in order to 8 | # allow any param to be assigned a value. 9 | class Type < Puppet::Resource::Type 10 | def valid_parameter?(name) 11 | true 12 | end 13 | end 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/integration/puppet/parser/functions/fixture_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | 3 | describe "the fixture function" do 4 | let(:the_node) { Puppet::Node.new('stub_node') } 5 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 6 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 7 | 8 | it "should load the fixture" do 9 | File.expects(:read).returns(:stub_file) 10 | expect(the_scope.function_fixture(["the fixture"])).to eq(:stub_file) 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/integration/puppet/parser/functions/stub_class_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | 3 | describe "the stub_class function" do 4 | let(:the_node) { Puppet::Node.new('stub_node') } 5 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 6 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 7 | 8 | it "should append a hostclass to the known resource types" do 9 | the_scope.function_stub_class(["stub class name"]) 10 | 11 | expect( 12 | the_scope.environment.known_resource_types.hostclass("stub class name").name 13 | ).to eq("stub class name") 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /spec/integration/puppet/parser/functions/stub_type_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | 3 | describe "the stub_type function" do 4 | let(:the_node) { Puppet::Node.new('stub_node') } 5 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 6 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 7 | 8 | it "should append a stub resource to the known resource types" do 9 | the_scope.function_stub_type(["stub type name"]) 10 | 11 | expect( 12 | the_scope.environment.known_resource_types.definition("stub type name").name 13 | ).to eq("stub type name") 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /spec/acceptance/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class acceptance { 2 | 3 | class { 'another::class': 4 | param => "value", 5 | lkjsdflkjsdf => "123", 6 | } 7 | 8 | package { 'the package': 9 | ensure => $another, 10 | provider => 'gem', 11 | } 12 | 13 | file { '/tmp/test': 14 | ensure => present, 15 | content => $::osfamily, 16 | } 17 | 18 | service { 'the service': 19 | ensure => running, 20 | enable => true, 21 | } 22 | 23 | another::type { 'the other thing': 24 | ensure => 'around', 25 | } 26 | 27 | file { '/tmp/test2': 28 | ensure => present, 29 | content => "stub content\n", 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spec/integration/puppet/parser/functions/stub_facts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | 3 | describe "the stub_facts function" do 4 | let(:the_node) { Puppet::Node.new('stub_node') } 5 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 6 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 7 | 8 | it "should assign each topscope variable" do 9 | the_scope.function_stub_facts([{ 10 | 'osfamily' => 'not a real os', 11 | 'ipaddress' => 'not a real ip', 12 | }]) 13 | 14 | expect(the_compiler.topscope['osfamily']).to eq('not a real os') 15 | expect(the_compiler.topscope['ipaddress']).to eq('not a real ip') 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /spec/integration/puppet/util/assertion/printer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/printer' 2 | 3 | describe Puppet::Util::Assertion::Printer do 4 | subject do 5 | class StubClass; include Puppet::Util::Assertion::Printer::Styler; end 6 | StubClass.new 7 | end 8 | 9 | context "given a proc" do 10 | it "should print the correct string" do 11 | subject.expects(:print).with("\e[0;31mthe red string\e[0m\n\e[0;34mthe blue string\e[0m\n\e[0;33mthe yellow string\e[0m\nthe white string") 12 | 13 | subject.style do 14 | red "the red string" 15 | newline 16 | blue "the blue string" 17 | newline 18 | yellow "the yellow string" 19 | newline 20 | white "the white string" 21 | end 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'puppet-spec' 5 | s.version = JSON.load(File.read('./metadata.json'))["version"] 6 | s.license = 'Apache-2.0' 7 | s.platform = Gem::Platform::RUBY 8 | s.homepage = 'http://github.com/jolshevski/puppet-spec' 9 | s.summary = 'Test Puppet code with Puppet code' 10 | s.description = 'A Puppet testing framework implemented in the native DSL' 11 | s.authors = ['Jordan Olshevski'] 12 | s.email = ['jordan@puppetlabs.com'] 13 | s.files = Dir.glob('lib/**/*') 14 | s.add_runtime_dependency 'puppet', ENV['PUPPET_VERSION'] 15 | s.add_development_dependency 'puppetlabs_spec_helper', '0.10.3' 16 | end 17 | -------------------------------------------------------------------------------- /spec/integration/puppet/type/assertion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'puppet/type/assertion' 3 | 4 | describe Puppet::Type.type(:assertion) do 5 | let(:the_subject) { stub(:is_a? => Puppet::Resource) } 6 | 7 | subject do 8 | Puppet::Type.type(:assertion).new( 9 | :name => 'stub name', 10 | :subject => the_subject, 11 | :attribute => 'stub attribute', 12 | :expectation => 'stub expectation', 13 | ) 14 | end 15 | 16 | it "should assign the name" do 17 | expect(subject[:name]).to eq('stub name') 18 | end 19 | 20 | it "should assign the subject" do 21 | expect(subject[:subject]).to eq(the_subject) 22 | end 23 | 24 | it "should assign the attribute" do 25 | expect(subject[:attribute]).to eq('stub attribute') 26 | end 27 | 28 | it "should assign the expectation" do 29 | expect(subject[:expectation]).to eq('stub expectation') 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | ## 2015-09-30 - 1.2.0 2 | #### Improvements 3 | - Issue #3 - Regex expectations 4 | - Issue #14 - Negative assertions 5 | 6 | ## 2015-09-09 - 1.1.1 7 | #### Improvements 8 | - Issue #4 - Documentation Corrections 9 | - Issue #22 - Better handling of non-existent assertion subjects 10 | 11 | ## 2015-09-08 - 1.1.0 12 | #### Improvements 13 | - Issue #21 - Refactor console output 14 | - Issue #19 - Fix the --manifest flag 15 | - Issue #17 - Move assertion input validation to the type 16 | - Issue #6 - Allow stubbed classes to receive arbitrary parameters 17 | - Issue #5 - CLI option for specifying single spec manifest 18 | 19 | ## 2015-09-03 - 1.0.2 20 | #### Improvements 21 | - Issue #12 - Gem / Rake tasks 22 | - Issue #2 - Output file and line number of the failed assertion's subject 23 | 24 | ## 2015-09-01 - 1.0.1 25 | #### Fixes 26 | - Issue #1 - Application now exits 1 if a failed assertion was encountered during spec evaluation. 27 | 28 | ## 2015-08-31 - 1.0.0 29 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/rake_tasks' 2 | 3 | task(:acceptance) do 4 | rake_result = `cd spec/acceptance; bundle exec rake puppetspec` 5 | cli_result = `cd spec/acceptance; bundle exec puppet spec` 6 | expectation = "\e[0;31m1) Assertion that the configuration file has the correct contents failed on File[/tmp/test]\e[0m\n\e[0;33m On line 13 of init.pp\e[0m\n\e[0;34m Wanted: \e[0m{\"content\"=>\"not the contents\"}\n\e[0;34m Got: \e[0m{\"content\"=>\"the contents\"}\n\n\e[0;31m2) Assertion that the resource is in the catalog failed on File[/tmp/should/be/around]\e[0m\n\e[0;34m Subject was expected to be present in the catalog, but was absent\e[0m\n\n\e[0;33mEvaluated 9 assertions\e[0m\n" 7 | 8 | unless rake_result == expectation 9 | puts rake_result.inspect, expectation.inspect 10 | raise "Check yoself, Rakefile acceptance is failing." 11 | end 12 | 13 | unless cli_result == expectation 14 | puts cli_result.inspect, expectation.inspect 15 | raise "Check yoself, CLI acceptance is failing." 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/puppet/provider/assertion/evaluator.rb: -------------------------------------------------------------------------------- 1 | Puppet::Type.type(:assertion).provide(:evaluator) do 2 | 3 | # Returns the complete path to the 4 | # subject's manifest after /manifests 5 | def relative_path 6 | @resource[:subject].file.split('manifests/').last 7 | end 8 | 9 | # Returns a hash containing the assertion's 10 | # attribute as the single key, with the value 11 | # of the expectation. Used for rendering the 12 | # results to the console. 13 | def wanted 14 | { @resource[:attribute] => @resource[:expectation] } 15 | end 16 | 17 | # Returns a hash containing the assertion's 18 | # attribute as the single key, with the value 19 | # of the subject's attribute. Used for rendering the 20 | # results to the console. 21 | def got 22 | { @resource[:attribute] => @resource[:subject][@resource[:attribute]] } 23 | end 24 | 25 | # Return false if the subject's attribute is 26 | # equal to the expectation, true otherwise. 27 | def failed? 28 | @resource[:expectation] != @resource[:subject][@resource[:attribute]] 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /lib/puppet/util/assertion/printer.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/colors' 2 | 3 | module Puppet::Util 4 | module Assertion 5 | class Printer 6 | include Puppet::Util::Colors 7 | 8 | attr_reader :stack 9 | 10 | def initialize 11 | @stack = [] 12 | end 13 | 14 | def red(msg) 15 | stack << colorize(:red, msg) 16 | end 17 | 18 | def blue(msg) 19 | stack << colorize(:blue, msg) 20 | end 21 | 22 | def yellow(msg) 23 | stack << colorize(:yellow, msg) 24 | end 25 | 26 | def white(msg) 27 | stack << msg 28 | end 29 | 30 | def newline 31 | stack << "\n" 32 | end 33 | 34 | def to_s 35 | stack.join 36 | end 37 | 38 | # Styler is a mixin that provides a helper 39 | # method that parses a styled string by 40 | # evaluating the given proc on an instance 41 | # of Printer. 42 | module Styler 43 | def style(&proc) 44 | printer = Puppet::Util::Assertion::Printer.new 45 | printer.instance_eval(&proc) 46 | print printer.to_s 47 | end 48 | end 49 | 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/unit/puppet/parser/functions/stub_fact_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | 3 | describe "the stub_fact function" do 4 | let(:the_node) { Puppet::Node.new('stub_node') } 5 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 6 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 7 | let(:the_topscope) { stub(:[]= => nil) } 8 | 9 | before do 10 | the_compiler.stubs(:topscope).returns(the_topscope) 11 | end 12 | 13 | context "when given a string" do 14 | it "should raise an error" do 15 | expect{the_scope.function_stub_facts(["stub string"])}.to raise_error( 16 | 'stub_facts accepts a hash of fact/value pairs' 17 | ) 18 | end 19 | end 20 | 21 | context "when given a hash of fact/value pairs" do 22 | let(:the_facts) {{ 23 | 'stubfact1' => 'stubvalue1', 24 | 'stubfact2' => 'stubvalue2', 25 | }} 26 | 27 | it "should set each value" do 28 | the_topscope.expects(:[]=).once.with('stubfact1', 'stubvalue1') 29 | the_topscope.expects(:[]=).once.with('stubfact2', 'stubvalue2') 30 | the_scope.function_stub_facts([the_facts]) 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/integration/puppet/application/spec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'puppet/application/spec' 3 | 4 | # Test the seam between the application, 5 | # reporter, and styler. 6 | describe Puppet::Application::Spec do 7 | describe ".run_command" do 8 | before do 9 | subject.stubs(:exit) 10 | STDOUT.stubs(:write) 11 | end 12 | 13 | context "when an error is not raised" do 14 | before { subject.stubs(:evaluate_assertions) } 15 | it "should exit 0" do 16 | subject.expects(:exit).with(0) 17 | subject.run_command 18 | end 19 | 20 | it "should print the expected message to the STDOUT" do 21 | STDOUT.expects(:write).with("\e[0;33mEvaluated 0 assertions\e[0m\n") 22 | subject.run_command 23 | end 24 | end 25 | 26 | context "when an error is raised" do 27 | before { subject.stubs(:evaluate_assertions).raises("stub error") } 28 | it "should exit 1" do 29 | subject.expects(:exit).with(1) 30 | subject.run_command 31 | end 32 | 33 | it "should print the expected message to the STDOUT" do 34 | STDOUT.expects(:write).with("\e[0;31mstub error\e[0m\n") 35 | subject.run_command 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/puppet/parser/functions/stub_class_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | 3 | describe "the stub_class function" do 4 | let(:the_node) { Puppet::Node.new('stub_node') } 5 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 6 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 7 | let(:the_environment) { stub(:name => nil, :known_resource_types => the_resource_types) } 8 | let(:the_resource_types) { stub(:<< => nil) } 9 | 10 | before do 11 | the_compiler.stubs(:environment).returns(the_environment) 12 | Puppet::Resource::Type.stubs(:new).returns(:stub_type) 13 | end 14 | 15 | context "when given a hash" do 16 | it "should raise an error" do 17 | expect{the_scope.function_stub_class([{}])}.to raise_error( 18 | 'stub_class accepts a class name in the form of a string' 19 | ) 20 | end 21 | end 22 | 23 | context "when given string" do 24 | it "should instantiate a hostclass" do 25 | Puppet::Resource::Type.expects(:new).with(:hostclass, "stub class name") 26 | the_scope.function_stub_class(["stub class name"]) 27 | end 28 | 29 | it "should append the hostclass to the environment's known resource types" do 30 | the_resource_types.expects(:<<).with(:stub_type) 31 | the_scope.function_stub_class(["stub class name"]) 32 | end 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/puppet/parser/functions/stub_type_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'puppet/parser/functions/stub_type' 3 | 4 | describe "the stub_type function" do 5 | let(:the_node) { Puppet::Node.new('stub_node') } 6 | let(:the_compiler) { Puppet::Parser::Compiler.new(the_node) } 7 | let(:the_scope) { Puppet::Parser::Scope.new(the_compiler) } 8 | let(:the_environment) { stub(:name => nil, :known_resource_types => the_resource_types) } 9 | let(:the_resource_types) { stub(:<< => nil) } 10 | 11 | before do 12 | the_compiler.stubs(:environment).returns(the_environment) 13 | Puppet::Util::Assertion::Stubs::Type.stubs(:new).returns(:stub_type) 14 | end 15 | 16 | context "when given a hash" do 17 | it "should raise an error" do 18 | expect{the_scope.function_stub_type([{}])}.to raise_error( 19 | 'stub_type accepts a type name in the form of a string' 20 | ) 21 | end 22 | end 23 | 24 | context "when given string" do 25 | it "should instantiate a stub type" do 26 | Puppet::Util::Assertion::Stubs::Type.expects(:new).with(:definition, "stub type name") 27 | the_scope.function_stub_type(["stub type name"]) 28 | end 29 | 30 | it "should append the type stub to the environment's known resource types" do 31 | the_resource_types.expects(:<<).with(:stub_type) 32 | the_scope.function_stub_type(["stub class name"]) 33 | end 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jordan-spec", 3 | "version": "1.2.0", 4 | "author": "Jordan Olshevski", 5 | "summary": "Test Puppet code with Puppet code", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/jolshevski/puppet-spec", 8 | "project_page": "https://github.com/jolshevski/puppet-spec", 9 | "issues_url": "https://github.com/jolshevski/puppet-spec/issues", 10 | "description": "A streamlined testing framework in the Puppet DSL", 11 | "operatingsystem_support": [ 12 | { 13 | "operatingsystem": "RedHat", 14 | "operatingsystemrelease": [ 15 | "4", 16 | "5", 17 | "6", 18 | "7" 19 | ] 20 | }, 21 | { 22 | "operatingsystem": "CentOS", 23 | "operatingsystemrelease": [ 24 | "4", 25 | "5", 26 | "6", 27 | "7" 28 | ] 29 | }, 30 | { 31 | "operatingsystem": "Debian", 32 | "operatingsystemrelease": [ 33 | "6", 34 | "7" 35 | ] 36 | }, 37 | { 38 | "operatingsystem": "Ubuntu", 39 | "operatingsystemrelease": [ 40 | "10.04", 41 | "12.04", 42 | "14.04" 43 | ] 44 | }, 45 | { 46 | "operatingsystem": "Windows", 47 | "operatingsystemrelease": [ 48 | "Server 2003", 49 | "Server 2003 R2", 50 | "Server 2008", 51 | "Server 2008 R2", 52 | "Server 2012", 53 | "Server 2012 R2", 54 | "7", 55 | "8" 56 | ] 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /spec/acceptance/spec/init_spec.pp: -------------------------------------------------------------------------------- 1 | stub_facts({ 2 | osfamily => 'the contents', 3 | another => '1.2.3', 4 | }) 5 | 6 | stub_class("another::class") 7 | 8 | stub_type("another::type") 9 | 10 | include acceptance 11 | 12 | assertion { 'that the package should be the correct version': 13 | subject => Package['the package'], 14 | attribute => 'ensure', 15 | expectation => '1.2.3', 16 | } 17 | 18 | assertion { 'that the configuration file has the correct contents': 19 | subject => File['/tmp/test'], 20 | attribute => 'content', 21 | expectation => 'not the contents', 22 | } 23 | 24 | assertion { 'that the service should start on boot': 25 | subject => Service['the service'], 26 | attribute => 'enable', 27 | expectation => true, 28 | } 29 | 30 | assertion { 'that the class containing all the other stuff should be included': 31 | subject => Class['another::class'], 32 | } 33 | 34 | assertion { 'that the class should have a gibberish attribute': 35 | subject => Class['another::class'], 36 | attribute => 'lkjsdflkjsdf', 37 | expectation => '123', 38 | } 39 | 40 | assertion { 'that the other thing is around': 41 | subject => Another::Type['the other thing'], 42 | attribute => 'ensure', 43 | expectation => 'around', 44 | } 45 | 46 | assertion { 'that the resource is in the catalog': 47 | subject => File['/tmp/should/be/around'], 48 | } 49 | 50 | assertion { 'that the undesired file is not in the catalog': 51 | ensure => absent, 52 | subject => File['/tmp/should/not/be/around'], 53 | } 54 | 55 | assertion { 'that the other configuration file has the correct contents': 56 | subject => File['/tmp/test2'], 57 | attribute => 'content', 58 | expectation => fixture('test2'), 59 | } 60 | -------------------------------------------------------------------------------- /spec/integration/puppet/provider/assertion/evaluator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/provider/assertion/evaluator' 2 | 3 | describe Puppet::Type.type(:assertion).provider(:evaluator) do 4 | let(:the_actual) { :stub_actual } 5 | 6 | let(:the_subject) do 7 | res = Puppet::Resource.new(:stub_type, :stub_title) 8 | res.file = '/test/directory/manifests/stub_namespace/stub_file.pp' 9 | res[:stub_attribute] = the_actual 10 | res 11 | end 12 | 13 | subject do 14 | Puppet::Type.type(:assertion).new( 15 | :name => :stub_name, 16 | :subject => the_subject, 17 | :attribute => :stub_attribute, 18 | :expectation => :stub_expectation, 19 | ).to_resource.to_ral.provider 20 | end 21 | 22 | describe ".relative_path" do 23 | it "should return the path to the subject's manifest after /manifests" do 24 | expect(subject.relative_path).to eq('stub_namespace/stub_file.pp') 25 | end 26 | end 27 | 28 | describe ".wanted" do 29 | it "should return a hash of the attribute and expected value" do 30 | expect(subject.wanted).to eq({:stub_attribute => :stub_expectation}) 31 | end 32 | end 33 | 34 | describe ".got" do 35 | it "should return a hash of the attribute and subject's actual value" do 36 | expect(subject.got).to eq({:stub_attribute => :stub_actual}) 37 | end 38 | end 39 | 40 | describe ".failed?" do 41 | context "when the subject's attribute is not equal to the expectation" do 42 | let(:the_actual) { :not_the_stub_expectation } 43 | 44 | it "should return true" do 45 | expect(subject.failed?).to be true 46 | end 47 | end 48 | 49 | context "when the subject's attribute is equal to the expectation" do 50 | let(:the_actual) { :stub_expectation } 51 | 52 | it "should return false" do 53 | expect(subject.failed?).to be false 54 | end 55 | end 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | env: 3 | - PUPPET_VERSION=4.3.0 4 | - PUPPET_VERSION=4.2.2 5 | - PUPPET_VERSION=4.2.1 6 | - PUPPET_VERSION=4.2.0 7 | - PUPPET_VERSION=4.1.0 8 | - PUPPET_VERSION=4.0.0 9 | rvm: 10 | - 2.2.3 11 | - 2.2.2 12 | - 2.2.1 13 | - 2.2.0 14 | - 2.1.0 15 | - 2.0.0 16 | - 1.9.3 17 | script: 18 | - bundle exec rake spec 19 | - bundle exec rake acceptance 20 | - bundle exec puppet module build 21 | - bundle exec gem build .gemspec 22 | - cp pkg/*.tar.gz jordan-spec.tar.gz 23 | deploy: 24 | - provider: releases 25 | skip_cleanup: true 26 | file: jordan-spec.tar.gz 27 | on: 28 | tags: true 29 | repo: jolshevski/puppet-spec 30 | branch: master 31 | api_key: 32 | secure: J9M66JNkANXCZ52bG07KMSQvI6S1HnQh1D39eI70MkLmdJ1xfaBK80jcf8wJklv4c2ZBsnI5/JRpUITiRh9r/di9v0BXIbDEYDHQV27u5BpU/0NV3KzFFi2j/PZnY1rY4k3NczoHRAAqlK+oAZtgZZZrtAX8mpDg4sGpw7QXkOEbnvTbNErk05Rg0mb68aONrhjLQLVeERXS6Ij2KF6hl6rzxPCfUjTdUiKv+bCBZb4UUkaTv/vntBYoRA/E6MDKxhpF6vuoZhx8qZe/Xnsn0MfjEs2ojbuLeHgpvz2xc8dUplCeNcIk/2JQ/hRCZyVsZjbC0sxYUjw3GeoGjeopYzhV0YIfMsb7pvxrD6ZMx//HnRH+6YtSoUqZIfXTJfRIuwsWAhbAsjNm4pnJheRFjDCLId52MTDUvwQVjCKaNX2EXdQyIT71mn/JHzJLfO/hbixaikg1OvHtty2rAOLym/7LuNWmgvtitKCAldbwgETc5yPOZNnhMiSfsIkX3JpzQ8Xo7gIN18IhBTKaDCoU6MerCIUYRzxuN8fZifTISgrkf5QA6wUXDL8D/nMnzD/KWGO3D0d/adoKpeEp2cSaSohv7p0e5ines6svnWIxWJu6wrSk0LUC8d9wJP46Pb6LnPieFGBNJF7nI1IyxUORz3L7oYXyOCDXD20vvNz93Q0= 33 | - provider: rubygems 34 | skip_cleanup: true 35 | gem: puppet-spec 36 | gemspec: .gemspec 37 | on: 38 | tags: true 39 | repo: jolshevski/puppet-spec 40 | branch: master 41 | api_key: 42 | secure: cEPat4fEsy7U48U7XXgZhJYoVVt/0r5N3eyGvrcvLPryCmtDAQdrfEH/2F8H1xT4TJEh3RNZs3caOAjNDZMD/IJ6gBeFnUi/8pR9p1qfuOc+XZaNVE07e6xuMPLaFxkKaIx1iUDvDmodeTGLtt3PuQismdDd1BjoBpunzRQbbaDfxPq0tz8TQWRGuQYcQ0xiwPfkNidHrMaUumU8nLcLcNljqQ9kEm1aQ/ZOli6nnneknZ3Yign7l9UcfSOkpDFIGSdpnIHpzWcBYaMLgvExaTFwMBrYkG6Fcr9f+4TKTYBP7f3Iq4hJMwUiPY17sdKXrZwgLB7bWsZCrzo36/u+Pewuo01Y3pZ9ZnFUgiSfDQFgg7bfXX6jPlZVwOom3Dri0QOdPvbbRQFYnye/i7GQZzeqjIYVbki+hhCjN9e6BTtTqrRTDGQ/6jirEJY6G6Vge8lnzUR0b3TPygJzvu4mwuxfpBRwRB7dEqR5vMtLuCazFfGTRnctElsxA0LdbuekjkRkyzKi6vxY6nMiCv4zjQioxm1oxhSQG+4elSBHFV6E8IqaqsZow6WZvYpuTI0m6GzALOkbrb/rrKrgPYIMxAYraY/9kpY0VF9sk0cgzO1rNtghyr5ly+tUpfSskiil5Bn2BdrWMymfknDe0GxL3q/ZOoLoOHn+1e8aunTPtOE= 43 | -------------------------------------------------------------------------------- /lib/puppet/type/assertion.rb: -------------------------------------------------------------------------------- 1 | Puppet::Type.newtype(:assertion) do 2 | 3 | @doc = "An assertion on the state of a resource in the catalog" 4 | 5 | validate do 6 | fail Puppet::Error, "a subject is required" unless @parameters[:subject] 7 | fail Puppet::Error, "an assertion on the absence of a resource cannot have an attribute" if @parameters[:attribute] and @parameters[:ensure].value == 'absent' 8 | fail Puppet::Error, "an assertion on the absence of a resource cannot have an expectation" if @parameters[:expectation] and @parameters[:ensure].value == 'absent' 9 | fail Puppet::Error, "an attribute is required when an expectation is given" if @parameters[:expectation] and not @parameters[:attribute] 10 | fail Puppet::Error, "an expectation is required when an attribute is given" if @parameters[:attribute] and not @parameters[:expectation] 11 | end 12 | 13 | newparam(:name) do 14 | desc "A plain text message describing what the assertion is attempting to prove. 15 | 16 | The given text should form a sentence using the type's name. 17 | Example: assertion { 'that the configuration file has the correct contents': } 18 | " 19 | end 20 | 21 | newparam(:subject) do 22 | desc "A reference to the resource to be asserted upon. 23 | 24 | The referenced resource will be the subject of any assertions made as a result of this resource declaration. 25 | " 26 | 27 | validate do |value| 28 | fail Puppet::Error, "Subject must be a resource reference" unless value.is_a? Puppet::Resource 29 | end 30 | end 31 | 32 | newparam(:attribute) do 33 | desc "An attribute of the subject resource to assert against" 34 | end 35 | 36 | newparam(:expectation) do 37 | desc "The expected value of the subject's attribute" 38 | end 39 | 40 | newparam(:ensure) do 41 | desc "If ensure is set to absent, the resource will assert for the absence of the subject in the catalog. 42 | 43 | Defaults to present. 44 | " 45 | 46 | defaultto "present" 47 | 48 | validate do |value| 49 | fail Puppet::Error, "Ensure only accepts values 'present' or 'absent'" unless value == 'present' or value == 'absent' 50 | end 51 | 52 | # Stub out the retrieve method since 53 | # the Puppet internals require any param 54 | # named ensure to have it. Grr. 55 | def retrieve 56 | end 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /spec/unit/puppet/util/assertion/printer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/printer' 2 | 3 | describe Puppet::Util::Assertion::Printer do 4 | describe "initialization" do 5 | it "should set the stack to an empty array" do 6 | expect(subject.stack).to eq([]) 7 | end 8 | end 9 | 10 | describe "the style methods" do 11 | let(:the_stack) { stub(:<< => nil) } 12 | 13 | before do 14 | subject.stubs(:stack).returns(the_stack) 15 | end 16 | 17 | describe ".red" do 18 | it "should colorize the msg and append it to the stack" do 19 | subject.expects(:colorize).with(:red, :stub_message).returns(:stub_colorized_string) 20 | the_stack.expects(:<<).with(:stub_colorized_string) 21 | subject.red(:stub_message) 22 | end 23 | end 24 | 25 | describe ".blue" do 26 | it "should colorize the msg and append it to the stack" do 27 | subject.expects(:colorize).with(:blue, :stub_message).returns(:stub_colorized_string) 28 | the_stack.expects(:<<).with(:stub_colorized_string) 29 | subject.blue(:stub_message) 30 | end 31 | end 32 | 33 | describe ".yellow" do 34 | it "should colorize the msg and append it to the stack" do 35 | subject.expects(:colorize).with(:yellow, :stub_message).returns(:stub_colorized_string) 36 | the_stack.expects(:<<).with(:stub_colorized_string) 37 | subject.yellow(:stub_message) 38 | end 39 | end 40 | 41 | describe ".white" do 42 | it "should append the message to the stack" do 43 | the_stack.expects(:<<).with(:stub_message) 44 | subject.white(:stub_message) 45 | end 46 | end 47 | 48 | describe ".newline" do 49 | it "should append a newline to the stack" do 50 | the_stack.expects(:<<).with("\n") 51 | subject.newline 52 | end 53 | end 54 | 55 | describe ".to_s" do 56 | it "should return the stack as a string" do 57 | the_stack.expects(:join).returns(:stub_stack) 58 | expect(subject.to_s).to eq(:stub_stack) 59 | end 60 | end 61 | end 62 | end 63 | 64 | describe Puppet::Util::Assertion::Printer::Styler do 65 | subject do 66 | class StubClass; include Puppet::Util::Assertion::Printer::Styler; end 67 | StubClass.new 68 | end 69 | 70 | describe ".style" do 71 | let(:the_printer) { stub(:to_s => :stub_output, :instance_eval => nil) } 72 | 73 | before do 74 | Puppet::Util::Assertion::Printer.stubs(:new).returns(the_printer) 75 | subject.stubs(:print) 76 | end 77 | 78 | it "should instantiate a printer" do 79 | Puppet::Util::Assertion::Printer.expects(:new).returns(the_printer) 80 | subject.style 81 | end 82 | 83 | it "should evaluate the proc on the printer" do 84 | the_printer.expects(:instance_eval) 85 | subject.style 86 | end 87 | 88 | it "should print the string" do 89 | subject.expects(:print).with(:stub_output) 90 | subject.style 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/puppet/application/spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'puppet/test/test_helper' 3 | require 'puppet/util/assertion/reporter' 4 | require 'fileutils' 5 | 6 | class Puppet::Application::Spec < Puppet::Application 7 | 8 | option("--manifest manifest", "-m manifest") 9 | 10 | attr_reader :reporter 11 | 12 | def run_command 13 | @reporter = Puppet::Util::Assertion::Reporter.new 14 | 15 | begin 16 | Puppet::Test::TestHelper.initialize 17 | evaluate_assertions 18 | reporter.print_footer 19 | rescue Exception => e 20 | reporter.print_error(e) 21 | end 22 | 23 | exit 1 unless reporter.failed == 0 24 | exit 0 25 | end 26 | 27 | def evaluate_assertions 28 | if options[:manifest] 29 | process_spec(options[:manifest]) 30 | else 31 | process_spec_directory(specdir) 32 | end 33 | end 34 | 35 | def process_spec_directory(specdir) 36 | Dir.glob("#{specdir}/**/*_spec.pp").map { |spec| process_spec(spec) } 37 | end 38 | 39 | def process_spec(path) 40 | catalog = catalog(path) 41 | 42 | assertions = catalog.resources.select {|res| res.type == 'Assertion' } 43 | assertions.each do |res| 44 | # Get the subject resource from the catalog rather than the 45 | # reference provided from the parser. The reference's resource 46 | # object does not contain any parameters for whatever reason. 47 | catalog_subject = catalog.resource(res[:subject].to_s) 48 | res[:subject] = catalog_subject if catalog_subject 49 | 50 | reporter << res.to_ral 51 | end 52 | end 53 | 54 | def catalog(path) 55 | Puppet::Test::TestHelper.before_each_test 56 | Puppet[:code] = File.read(path) 57 | 58 | node = Puppet::Node.new("spec") 59 | modulepath = get_modulepath(node) 60 | link_module(modulepath) 61 | catalog = Puppet::Resource::Catalog.indirection.find(node.name, :use_node => node) 62 | catalog.to_ral 63 | 64 | Puppet::Test::TestHelper.after_each_test 65 | catalog 66 | end 67 | 68 | # Given a node object, return 69 | # the first modulepath 70 | def get_modulepath(node) 71 | node.environment.full_modulepath[0] 72 | end 73 | 74 | # Ensure that a symlink is present 75 | # pointing from the node's env 76 | # to the current directory 77 | def link_module(modulepath) 78 | pwd = Dir.pwd 79 | name = File.basename(pwd) 80 | symlink = File.join(modulepath, name) 81 | 82 | # Ensure that the modulepath exists 83 | # within the temp environment 84 | FileUtils.mkdir_p(modulepath) unless Dir.exist?(modulepath) 85 | 86 | # Ensure that a symlink to the 87 | # cwd exists 88 | FileUtils.ln_s(pwd, symlink) unless File.symlink?(symlink) 89 | end 90 | 91 | # Return the specdir under the 92 | # CWD or raise an error if not 93 | # found. 94 | def specdir 95 | pwd = Dir.pwd 96 | specdir = File.join(pwd, 'spec') 97 | unless Dir.exist?(specdir) 98 | raise 'No spec directory was found under the CWD. A spec manifest can be specified with the --manifest flag' 99 | end 100 | specdir 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /spec/integration/puppet/util/assertion/reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'puppet/util/assertion/reporter' 3 | 4 | describe Puppet::Util::Assertion::Reporter do 5 | 6 | # Covers the integration of the assertion resource 7 | # type/provider with the reporter. 8 | describe ".<<" do 9 | before { subject.stubs(:print) } 10 | let(:the_subject_catalog) { :stub_catalog } 11 | 12 | context "with a single assertion resource" do 13 | let(:the_subject_value) { 'stub_value' } 14 | let(:the_assertion) do 15 | Puppet::Type.type(:assertion).new( 16 | :name => 'stub assertion 1', 17 | :attribute => 'stub_attribute', 18 | :expectation => 'stub_value', 19 | :subject => the_subject, 20 | ) 21 | end 22 | 23 | let(:the_subject) do 24 | subj = Puppet::Resource.new(:stub_type, :stub_title) 25 | subj['stub_attribute'] = the_subject_value 26 | subj.file = 'long/file/path/manifests/stub_namespace/stub_manifest.pp' 27 | subj.line = '123' 28 | subj.catalog = the_subject_catalog 29 | subj 30 | end 31 | 32 | context "with an assertion that expects the subject to be present" do 33 | before { the_assertion[:ensure] = 'present' } 34 | 35 | context "with no subject" do 36 | let(:the_subject_catalog) { nil } 37 | 38 | it "should print the expected message" do 39 | subject.expects(:print).with( 40 | "\e[0;31m1) Assertion stub assertion 1 failed on Stub_type[stub_title]\e[0m\n\e[0;34m Subject was expected to be present in the catalog, but was absent\e[0m\n\n" 41 | ) 42 | subject << the_assertion 43 | end 44 | end 45 | 46 | context "with a subject" do 47 | it "should not print a message" do 48 | subject.expects(:print).never 49 | subject << the_assertion 50 | end 51 | 52 | context "when the attribute's value matches the expectation" do 53 | let(:the_subject_value) { 'stub_value' } 54 | 55 | it "should print the expected output" do 56 | subject.expects(:print).never 57 | subject << the_assertion 58 | end 59 | end 60 | 61 | context "when the attribute's value does not match the expectation" do 62 | let(:the_subject_value) { 'stub_incorrect_value' } 63 | 64 | it "should print the expected output" do 65 | subject.expects(:print).with( 66 | "\e[0;31m1) Assertion stub assertion 1 failed on Stub_type[stub_title]\e[0m\n\e[0;33m On line 123 of stub_namespace/stub_manifest.pp\e[0m\n\e[0;34m Wanted: \e[0m{\"stub_attribute\"=>\"stub_value\"}\n\e[0;34m Got: \e[0m{\"stub_attribute\"=>\"stub_incorrect_value\"}\n\n" 67 | ) 68 | subject << the_assertion 69 | end 70 | end 71 | end 72 | end 73 | 74 | context "with an assertion that expects the subject to be absent" do 75 | before { the_assertion[:ensure] = 'absent' } 76 | 77 | context "with no subject" do 78 | let(:the_subject_catalog) { nil } 79 | 80 | it "should not print a message" do 81 | subject.expects(:print).never 82 | subject << the_assertion 83 | end 84 | end 85 | 86 | context "with a subject" do 87 | it "should print the expected message" do 88 | subject.expects(:print).with( 89 | "\e[0;31m1) Assertion stub assertion 1 failed on Stub_type[stub_title]\e[0m\n\e[0;34m Subject was expected to be absent from the catalog, but was present\e[0m\n\n" 90 | ) 91 | subject << the_assertion 92 | end 93 | end 94 | end 95 | 96 | end 97 | end 98 | 99 | describe ".print_error" do 100 | let(:the_error) { stub(:message => 'stub message') } 101 | 102 | before { subject.stubs(:print) } 103 | 104 | it "should print the expected message" do 105 | subject.expects(:print).with("\e[0;31mstub message\e[0m\n") 106 | subject.print_error(the_error) 107 | end 108 | 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/puppet/util/assertion/reporter.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/printer' 2 | 3 | module Puppet::Util 4 | module Assertion 5 | class Reporter 6 | include Puppet::Util::Assertion::Printer::Styler 7 | 8 | attr_reader :evaluated, :failed 9 | 10 | def initialize 11 | @evaluated = 0 12 | @failed = 0 13 | end 14 | 15 | # Given an assertion resource, evaluate it for success, 16 | # and on failure, call the appropriate method responsible to render the 17 | # message, and increment the counter(s). 18 | def <<(assertion) 19 | count 20 | 21 | # If ensure is present, and the resource is not in the catalog 22 | if assertion[:ensure] == 'present' and not assertion[:subject].catalog 23 | fail 24 | expected_present(assertion) 25 | return 26 | 27 | # If ensure is absent, and the resource is in the catalog 28 | elsif assertion[:ensure] == 'absent' and assertion[:subject].catalog 29 | fail 30 | expected_absent(assertion) 31 | return 32 | end 33 | 34 | if assertion.provider.failed? 35 | fail 36 | inequal_value(assertion) 37 | return 38 | end 39 | 40 | end 41 | 42 | # Print the summary of evaluated assertions 43 | def print_footer 44 | # Shim the reporter into the local scope 45 | reporter = self 46 | 47 | style do 48 | if reporter.evaluated == 1 49 | yellow "Evaluated 1 assertion" 50 | newline 51 | else 52 | yellow "Evaluated #{reporter.evaluated} assertions" 53 | newline 54 | end 55 | end 56 | end 57 | 58 | def print_error(err) 59 | fail #Mark an assertion so the application exits 1 60 | style do 61 | red err.message 62 | newline 63 | end 64 | end 65 | 66 | # Print the appropriate error message when an assertion's 67 | # subject is found in the catalog but was intended to be 68 | # absent. 69 | def expected_absent(assertion) 70 | # Shim the value of failed into the 71 | # local scope in order to access it 72 | # from the style proc. 73 | failed = @failed 74 | 75 | style do 76 | red "#{failed}) Assertion #{assertion[:name]} failed on #{assertion[:subject].to_s}" 77 | newline 78 | blue " Subject was expected to be absent from the catalog, but was present" 79 | newline 80 | newline 81 | end 82 | end 83 | 84 | # Print the appropriate error message when an assertion's 85 | # subject is not found in the catalog. 86 | def expected_present(assertion) 87 | # Shim the value of failed into the 88 | # local scope in order to access it 89 | # from the style proc. 90 | failed = @failed 91 | 92 | style do 93 | red "#{failed}) Assertion #{assertion[:name]} failed on #{assertion[:subject].to_s}" 94 | newline 95 | blue " Subject was expected to be present in the catalog, but was absent" 96 | newline 97 | newline 98 | end 99 | end 100 | 101 | # Pretty print the results of an assertion to the console 102 | def inequal_value(assertion) 103 | # Shim the value of failed into the 104 | # local scope in order to access it 105 | # from the style proc. 106 | failed = @failed 107 | 108 | style do 109 | red "#{failed}) Assertion #{assertion[:name]} failed on #{assertion[:subject].to_s}" 110 | newline 111 | 112 | yellow " On line #{assertion[:subject].line} of #{assertion.provider.relative_path}" 113 | newline 114 | 115 | blue " Wanted: " 116 | white assertion.provider.wanted.to_s 117 | newline 118 | 119 | blue " Got: " 120 | white assertion.provider.got.to_s 121 | newline 122 | newline 123 | end 124 | end 125 | 126 | def count 127 | @evaluated += 1 128 | end 129 | 130 | def fail 131 | @failed += 1 132 | end 133 | 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /spec/unit/puppet/util/assertion/reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/assertion/reporter' 2 | 3 | describe Puppet::Util::Assertion::Reporter do 4 | 5 | describe "the initialization" do 6 | it "should set the evaluated count to 0" do 7 | expect(subject.evaluated).to eq(0) 8 | end 9 | 10 | it "should set the failed count to 0" do 11 | expect(subject.failed).to eq(0) 12 | end 13 | end 14 | 15 | describe ".<<" do 16 | let(:the_provider) { stub(:failed? => false) } 17 | let(:the_resource) { stub(:provider => the_provider, :[] => nil) } 18 | let(:the_present_subject) { stub(:catalog => true) } 19 | let(:the_absent_subject) { stub(:catalog => false) } 20 | 21 | before do 22 | subject.stubs(:count) 23 | subject.stubs(:fail) 24 | subject.stubs(:expected_present) 25 | subject.stubs(:expected_absent) 26 | subject.stubs(:inequal_value) 27 | the_resource.stubs(:[]).with(:subject).returns(the_present_subject) 28 | end 29 | 30 | it "should increment the counter" do 31 | subject.expects(:count) 32 | subject << the_resource 33 | end 34 | 35 | context "when the subject is expected to be present but is absent" do 36 | before do 37 | the_resource.stubs(:[]).with(:ensure).returns('present') 38 | the_resource.stubs(:[]).with(:subject).returns(the_absent_subject) 39 | end 40 | 41 | it "should increment the fail counter" do 42 | subject.expects(:fail).once 43 | subject << the_resource 44 | end 45 | 46 | it "should print a message" do 47 | subject.expects(:expected_present).with(the_resource) 48 | subject << the_resource 49 | end 50 | end 51 | 52 | context "when the subject is expected to be present and is present" do 53 | before do 54 | the_resource.stubs(:[]).with(:ensure).returns('present') 55 | the_resource.stubs(:[]).with(:subject).returns(the_present_subject) 56 | end 57 | 58 | it "should evaluate the provider for failure" do 59 | the_provider.expects(:failed?).returns(:stub_results) 60 | subject << the_resource 61 | end 62 | 63 | context "if the assertion did not fail" do 64 | before { the_provider.stubs(:failed?).returns(false) } 65 | 66 | it "should not increment the fail counter" do 67 | subject.expects(:fail).never 68 | subject << the_resource 69 | end 70 | 71 | it "should not print the report" do 72 | subject.expects(:inequal_value).never 73 | subject << the_resource 74 | end 75 | end 76 | 77 | context "if the assertion failed" do 78 | before { the_provider.stubs(:failed?).returns(true) } 79 | 80 | it "should increment the fail counter" do 81 | subject.expects(:fail).once 82 | subject << the_resource 83 | end 84 | 85 | it "should print the report" do 86 | subject.expects(:inequal_value).with(the_resource) 87 | subject << the_resource 88 | end 89 | end 90 | end 91 | 92 | context "when the resource is expected to be absent and is present" do 93 | before do 94 | the_resource.stubs(:[]).with(:ensure).returns('absent') 95 | the_resource.stubs(:[]).with(:subject).returns(the_present_subject) 96 | end 97 | 98 | it "should increment the fail counter" do 99 | subject.expects(:fail).once 100 | subject << the_resource 101 | end 102 | 103 | it "should print a message" do 104 | subject.expects(:expected_absent).with(the_resource) 105 | subject << the_resource 106 | end 107 | end 108 | 109 | context "when the resource is expected to be absent and is absent" do 110 | before do 111 | the_resource.stubs(:[]).with(:ensure).returns('absent') 112 | the_resource.stubs(:[]).with(:subject).returns(the_absent_subject) 113 | end 114 | 115 | it "should not increment the fail counter" do 116 | subject.expects(:fail).never 117 | subject << the_resource 118 | end 119 | 120 | it "should not print a message" do 121 | subject.expects(:expected_absent).never 122 | subject << the_resource 123 | end 124 | end 125 | 126 | end 127 | 128 | describe ".print_footer" do 129 | it "should print the stylized footer" do 130 | subject.expects(:style) 131 | subject.print_footer 132 | end 133 | end 134 | 135 | describe ".print_error" do 136 | let(:the_error) { stub(:message => :stub_message) } 137 | 138 | before do 139 | subject.stubs(:style) 140 | subject.stubs(:fail) 141 | end 142 | 143 | it "should mark a failed assertion" do 144 | subject.expects(:fail) 145 | subject.print_error(the_error) 146 | end 147 | 148 | it "should print the error" do 149 | subject.expects(:style) 150 | subject.print_error(the_error) 151 | end 152 | end 153 | 154 | describe ".inequal_value" do 155 | it "should print the stylized assertion results" do 156 | subject.expects(:style) 157 | subject.inequal_value(:stub_resource) 158 | end 159 | end 160 | 161 | describe ".count" do 162 | it "should increment evaluated" do 163 | subject.count 164 | subject.count 165 | expect(subject.evaluated).to eq(2) 166 | end 167 | end 168 | 169 | describe ".fail" do 170 | it "should increment failed" do 171 | subject.fail 172 | subject.fail 173 | expect(subject.failed).to eq(2) 174 | end 175 | end 176 | 177 | end 178 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/assertion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'puppet/type/assertion' 3 | 4 | describe Puppet::Type.type(:assertion) do 5 | describe "validate" do 6 | context "when given a attribute, expectation, and subject" do 7 | subject do 8 | Puppet::Type.type(:assertion).new( 9 | :name => :stub_name, 10 | :attribute => :stub_attribute, 11 | :expectation => :stub_expectation, 12 | :subject => Puppet::Resource.new(:stub_type, :stub_subject), 13 | ) 14 | end 15 | 16 | it "should not raise an error" do 17 | expect{subject.validate}.to_not raise_error 18 | end 19 | end 20 | 21 | context "when given a attribute and expectation" do 22 | subject do 23 | Puppet::Type.type(:assertion).new( 24 | :name => :stub_name, 25 | :attribute => :stub_attribute, 26 | :expectation => :stub_expectation, 27 | ) 28 | end 29 | 30 | it "should raise an error" do 31 | expect{subject.validate}.to raise_error( 32 | 'Validation of Assertion[stub_name] failed: a subject is required' 33 | ) 34 | end 35 | end 36 | 37 | context "when given an attribute and no expectation" do 38 | subject do 39 | Puppet::Type.type(:assertion).new( 40 | :name => :stub_name, 41 | :attribute => :stub_attribute, 42 | :subject => Puppet::Resource.new(:stub_type, :stub_subject), 43 | ) 44 | end 45 | 46 | it "should raise an error" do 47 | expect{subject.validate}.to raise_error( 48 | 'Validation of Assertion[stub_name] failed: an expectation is required when an attribute is given' 49 | ) 50 | end 51 | end 52 | 53 | context "when given an expectation and no attribute" do 54 | subject do 55 | Puppet::Type.type(:assertion).new( 56 | :name => :stub_name, 57 | :expectation => :stub_expectation, 58 | :subject => Puppet::Resource.new(:stub_type, :stub_subject), 59 | ) 60 | end 61 | 62 | it "should raise an error" do 63 | expect{subject.validate}.to raise_error( 64 | 'Validation of Assertion[stub_name] failed: an attribute is required when an expectation is given' 65 | ) 66 | end 67 | end 68 | 69 | context "when given an expectation and ensure absent" do 70 | subject do 71 | Puppet::Type.type(:assertion).new( 72 | :name => :stub_name, 73 | :ensure => 'absent', 74 | :expectation => :stub_expectation, 75 | :subject => Puppet::Resource.new(:stub_type, :stub_subject), 76 | ) 77 | end 78 | 79 | it "should raise an error" do 80 | expect{subject.validate}.to raise_error( 81 | 'Validation of Assertion[stub_name] failed: an assertion on the absence of a resource cannot have an expectation' 82 | ) 83 | end 84 | end 85 | 86 | context "when given an attribute and ensure absent" do 87 | subject do 88 | Puppet::Type.type(:assertion).new( 89 | :name => :stub_name, 90 | :ensure => 'absent', 91 | :attribute => :stub_attribute, 92 | :subject => Puppet::Resource.new(:stub_type, :stub_subject), 93 | ) 94 | end 95 | 96 | it "should raise an error" do 97 | expect{subject.validate}.to raise_error( 98 | 'Validation of Assertion[stub_name] failed: an assertion on the absence of a resource cannot have an attribute' 99 | ) 100 | end 101 | end 102 | end 103 | end 104 | 105 | describe Puppet::Type::Assertion::ParameterSubject do 106 | let(:the_resource) { stub } 107 | subject { Puppet::Type::Assertion::ParameterSubject.new(:resource => the_resource) } 108 | 109 | describe ".validate" do 110 | context "when given a string" do 111 | it "should raise an error" do 112 | expect{subject.validate('test')}.to raise_error( 113 | 'Subject must be a resource reference' 114 | ) 115 | end 116 | end 117 | 118 | context "when given a hash" do 119 | it "should raise an error" do 120 | expect{subject.validate({:key => :value})}.to raise_error( 121 | 'Subject must be a resource reference' 122 | ) 123 | end 124 | end 125 | 126 | context "when given a Puppet resource" do 127 | let(:the_resource) do 128 | Puppet::Type.type(:package).new( 129 | :name => 'the resource', 130 | ).to_resource 131 | end 132 | 133 | it "should should not raise an error" do 134 | expect{subject.validate(the_resource)}.to_not raise_error 135 | end 136 | end 137 | end 138 | end 139 | 140 | describe Puppet::Type::Assertion::ParameterEnsure do 141 | let(:the_resource) { stub } 142 | subject { Puppet::Type::Assertion::ParameterEnsure.new(:resource => the_resource) } 143 | 144 | describe ".retrieve" do 145 | it "should respond" do 146 | expect(subject).to respond_to(:retrieve) 147 | end 148 | end 149 | 150 | describe ".validate" do 151 | context "when given foobar" do 152 | it "should raise an error" do 153 | expect{subject.validate("foobar")}.to raise_error( 154 | "Ensure only accepts values 'present' or 'absent'" 155 | ) 156 | end 157 | end 158 | 159 | context "when given absent" do 160 | it "should should not raise an error" do 161 | expect{subject.validate("absent")}.to_not raise_error 162 | end 163 | end 164 | 165 | context "when given present" do 166 | it "should should not raise an error" do 167 | expect{subject.validate("present")}.to_not raise_error 168 | end 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # puppet-spec 2 | [![Build Status](https://travis-ci.org/jolshevski/puppet-spec.svg?branch=master)](https://travis-ci.org/jolshevski/puppet-spec) 3 | 4 | Test Puppet code with Puppet code. 5 | 6 | ## Why not rspec-puppet? 7 | Puppet-spec is intended to provide a low barrier to entry for those new to testing, and is idiomatic for anyone familiar with the Puppet DSL. Rspec-puppet, while more powerful, is far more complex and requires past exposure to Ruby and rspec. Don't think of puppet-spec as an attempt to undermine the wide adoption of rspec-puppet in the community, but rather the fulfilment of an unmet, yet significant need. 8 | 9 | ## Getting Started 10 | ### Installation 11 | #### Puppet Module 12 | You can install the Puppet spec module by cloning this repository into your modulepath, or by running `puppet module install jordan/spec`. The `puppet spec` command will be available once the module has been installed. 13 | 14 | #### Rubygem 15 | If your Puppet module has a Gemfile, you can add the gem `puppet-spec` as a dependency and include the bundled rake task to simplify the process of invoking your test suite. 16 | Just add `require puppet-spec/tasks` to your Rakefile, and run `rake puppetspec` to invoke the test suite. 17 | 18 | ### Requirements 19 | Puppet spec is tested on all permutations of the below versions. 20 | 21 | Puppet: 22 | * 4.2.1 23 | * 4.2.0 24 | * 4.1.0 25 | * 4.0.0 26 | 27 | Ruby: 28 | * 2.2.0 29 | * 2.1.0 30 | * 2.0.0 31 | * 1.9.3 32 | 33 | ### A simple test case 34 | ```puppet 35 | file { '/tmp/test': 36 | ensure => present, 37 | content => 'The file content', 38 | } 39 | 40 | assertion { 'that the file has the correct contents': 41 | subject => File['/tmp/test'], 42 | attribute => 'content', 43 | expectation => 'The file content', 44 | } 45 | 46 | assertion { 'that the file is present': 47 | subject => File['/tmp/test'], 48 | attribute => 'ensure', 49 | expectation => 'present', 50 | } 51 | ``` 52 | 53 | #### The Assertion Resource Type 54 | ##### Title 55 | The assertion's title should reflect what it is attempting to prove. This value will not used during evaluation, and serves only to provide insight to the user in the event that the assertion fails. 56 | 57 | ##### Subject 58 | A resource reference to the resource under test, i.e. `File['/etc/puppetlabs/puppet/puppet.conf']`. 59 | 60 | ##### Attribute 61 | If the attribute parameter is not provided, the assertion will prove only the presence of the subject resource in the catalog. 62 | Otherwise, attribute can be used to select which attribute of the subject resource the assertion will set an expectation on. 63 | 64 | ##### Expectation 65 | Expectation should be set to the expected value of the subject's attribute as determined by the `subject` and `attribute` params. It's required if attribute is provided. 66 | 67 | ##### Ensure 68 | Defaults to 'present', and accepts values 'present' and 'absent'. If the ensure attribute is set to absent, the assertion will validate that the subject is absent from the catalog. Expectation and attribute cannot be set in conjunction with ensure => 'absent'. 69 | 70 | 71 | Puppet spec test cases are just manifests that contain assertion resource declarations. A typical test case is essentially an example (smoke test) with assertions. 72 | The assertions prove that the referenced resources have been included in the catalog and have the expected attributes. Test cases tend to look something like the below. 73 | ```puppet 74 | include apache 75 | 76 | assertion { 'that the apache::ssl class is in the catalog': 77 | subject => Class['apache::ssl'], 78 | } 79 | 80 | assertion { 'that the apache package version is correct': 81 | subject => Package['apache::package'], 82 | attribute => 'ensure', 83 | expectation => '1.2.3', 84 | } 85 | ... 86 | ``` 87 | 88 | Any spec manifests stored in the module-under-test's `spec` directory with a `*_spec.pp` suffix will be evaluated when `puppet spec` is executed. Failed assertions are printed to the console rspec style. 89 | 90 | 91 | ## Stubbing 92 | In order to effectively unit test a class, we may need to stub certain dependencies and/or potential side affects. Puppet spec provides three functions that can be used to stub at the Puppet compiler's top scope. Naturally, test cases are parsed top down, so the stub functions must be called before any affected resources are evaluated. 93 | 94 | For examples, check out the acceptance test suite. 95 | 96 | #### stub_facts({}) 97 | Stub_facts takes a hash of fact/value pairs, and... stubs them. Technically it just defines top scope variables, but stub_top_scope_variables() doesn't quite roll off the tongue. 98 | 99 | #### stub_class('') 100 | Stub_class stubs a given class. The stubbed class will accept values for any param. Classes can be namespaced as expected with the scope indicator. 101 | 102 | #### stub_type('') 103 | Stub_type stubs a defined type. Any parameters will be accepted and can be asserted upon. Like the stub_class function, the type name can be namespaced as you would expect. 104 | 105 | 106 | ## Fixtures 107 | Asserting on attributes with a long value can cause test suites to become unmanageably large, so Puppet spec provides a `fixture` function which reads from a given file underneath `spec/fixtures`. 108 | 109 | ### Example 110 | ```puppet 111 | assertion { 'that the file has the correct very long contents': 112 | subject => File['/tmp/largefile'], 113 | attribute => 'content', 114 | expectation => fixture('file_contents'), #This would load the file `/spec/fixtures/file_contents` 115 | } 116 | ``` 117 | 118 | ## Negative Assertions 119 | Considering that Puppet modules often make use of logical expressions to exclude resources from the catalog, Puppet spec's assertion resource type has an ensure attribute, which when given the value `absent`, sets an expectation on the absence of a resource from the catalog. 120 | 121 | ```puppet 122 | assertion { 'that the undesired file is not in the catalog': 123 | ensure => absent, 124 | subject => File['/tmp/should/not/be/around'], 125 | } 126 | ``` 127 | 128 | ## Want to pitch in? 129 | I wrote this tool because I felt that the community could use an approachable testing mechanism in Puppet's native tongue. If you feel the same, feel free to take on an open GH issue, or find a bug. 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /spec/unit/puppet/application/spec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'puppet/application/spec' 3 | 4 | describe Puppet::Application::Spec do 5 | 6 | describe ".handle_manifest" do 7 | it "should set the manifest configuration" do 8 | subject.handle_manifest(:stub_manifest) 9 | expect(subject.options[:manifest]).to eq(:stub_manifest) 10 | end 11 | end 12 | 13 | describe ".run_command" do 14 | let(:the_reporter) { stub(:print_error => nil, :print_footer => nil, :failed => 0) } 15 | 16 | before do 17 | Puppet::Test::TestHelper.stubs(:initialize) 18 | Puppet::Util::Assertion::Reporter.stubs(:new).returns(the_reporter) 19 | subject.stubs(:reporter).returns(the_reporter) 20 | subject.stubs(:evaluate_assertions) 21 | subject.stubs(:exit) 22 | end 23 | 24 | it "should instantiate a reporter" do 25 | expect(subject.reporter).to eq(the_reporter) 26 | end 27 | 28 | it "should initialize Puppet" do 29 | Puppet::Test::TestHelper.expects(:initialize) 30 | subject.run_command 31 | end 32 | 33 | it "should evaluate the assertions" do 34 | subject.expects(:evaluate_assertions) 35 | subject.run_command 36 | end 37 | 38 | it "should print the footer" do 39 | the_reporter.expects(:print_footer) 40 | subject.run_command 41 | end 42 | 43 | context "when an error is not raised" do 44 | it "should not print to the console" do 45 | the_reporter.expects(:print_error).never 46 | subject.run_command 47 | end 48 | end 49 | 50 | context "when an error is raised" do 51 | let(:the_error) { Exception.new('stub exception') } 52 | before { subject.stubs(:evaluate_assertions).raises(the_error) } 53 | 54 | it "should report it" do 55 | the_reporter.expects(:print_error).with(the_error) 56 | subject.run_command 57 | end 58 | end 59 | 60 | context "when the reporter contains 3 failures" do 61 | before { the_reporter.stubs(:failed).returns(3) } 62 | it "should exit 1" do 63 | subject.expects(:exit).with(1) 64 | subject.run_command 65 | end 66 | end 67 | 68 | context "when the reporter contains 0 failures" do 69 | before { the_reporter.stubs(:failed).returns(0) } 70 | 71 | it "should exit 0" do 72 | subject.expects(:exit).with(0) 73 | subject.run_command 74 | end 75 | end 76 | end 77 | 78 | describe ".evaluate_assertions" do 79 | let(:the_manifest) { nil } 80 | 81 | before do 82 | subject.stubs(:specdir).returns(:stub_directory) 83 | subject.stubs(:options).returns({ 84 | :manifest => the_manifest, 85 | }) 86 | end 87 | 88 | context "when the manifest option hasn't been set" do 89 | it "should process the spec directory" do 90 | subject.expects(:process_spec_directory).with(:stub_directory) 91 | subject.evaluate_assertions 92 | end 93 | end 94 | 95 | context "when the manifest option has been set" do 96 | let(:the_manifest) { :stub_manifest } 97 | it "should process the given manifest" do 98 | subject.expects(:process_spec).with(:stub_manifest) 99 | subject.evaluate_assertions 100 | end 101 | end 102 | end 103 | 104 | describe ".process_spec_directory" do 105 | let(:the_files) {[ 106 | :stub_spec1, 107 | :stub_spec2, 108 | :stub_spec3, 109 | ]} 110 | 111 | before do 112 | subject.stubs(:process_spec).with(:stub_spec1).returns(:stub_result1) 113 | subject.stubs(:process_spec).with(:stub_spec2).returns(:stub_result2) 114 | subject.stubs(:process_spec).with(:stub_spec3).returns([:stub_result3]) 115 | Dir.stubs(:glob).returns(the_files) 116 | end 117 | 118 | it "should evaluate the specdir" do 119 | Dir.expects(:glob).with('stub_path/**/*_spec.pp').returns(the_files) 120 | subject.process_spec_directory('stub_path') 121 | end 122 | 123 | it "should process each spec" do 124 | subject.expects(:process_spec).once.with(the_files[0]) 125 | subject.expects(:process_spec).once.with(the_files[1]) 126 | subject.expects(:process_spec).once.with(the_files[2]) 127 | subject.process_spec_directory('stub_path') 128 | end 129 | end 130 | 131 | describe ".process_spec" do 132 | let(:the_catalog) { stub(:resources => the_resources) } 133 | let(:the_reporter) { stub(:<< => nil, :missing_subject => nil) } 134 | let(:the_resources) {[ 135 | stub(:[]= => nil, :type => 'Assertion', :[] => :stub_subject1, :to_ral => :stub_resource), 136 | stub(:[]= => nil, :type => 'Not an Assertion', :[] => :stub_subject2), 137 | ]} 138 | 139 | before do 140 | subject.stubs(:reporter).returns(the_reporter) 141 | subject.stubs(:catalog).returns(the_catalog) 142 | the_catalog.stubs(:resource).with('stub_subject1').returns(:stub_catalog_resource) 143 | subject.stubs(:evaluate) 144 | subject.stubs(:visit_assertions).returns(:stub_assertions) 145 | end 146 | 147 | it "should compile the catalog" do 148 | subject.expects(:catalog).with(:stub_path).returns(the_catalog) 149 | subject.process_spec(:stub_path) 150 | end 151 | 152 | context "when the subject is found in the catalog" do 153 | before { the_catalog.stubs(:resource).with('stub_subject1').returns(:stub_catalog_resource) } 154 | 155 | it "should pass it to the reporter" do 156 | the_reporter.expects(:<<).once.with(the_resources[0].to_ral) 157 | subject.process_spec(:stub_path) 158 | end 159 | 160 | it "should set each assertion subject from the catalog" do 161 | the_resources[0].expects(:[]=).with(:subject, :stub_catalog_resource) 162 | the_resources[1].expects(:[]=).never 163 | subject.process_spec(:stub_path) 164 | end 165 | end 166 | 167 | context "when the subject is not found in the catalog" do 168 | before { the_catalog.stubs(:resource).with('stub_subject1').returns(nil) } 169 | 170 | it "should not pass it to the reporter" do 171 | the_reporter.expects(:<<).never 172 | subject.process_spec(:stub_path) 173 | end 174 | 175 | it "should not set each assertion subject from the catalog" do 176 | the_resources[0].expects(:[]=).never 177 | the_resources[1].expects(:[]=).never 178 | subject.process_spec(:stub_path) 179 | end 180 | end 181 | end 182 | 183 | describe ".catalog" do 184 | let(:the_node) { stub('node', :name => :stub_name) } 185 | let(:the_catalog) { stub(:to_ral => nil) } 186 | let(:the_indirection) { stub('indirection', :find => the_catalog) } 187 | 188 | before do 189 | Puppet::Test::TestHelper.stubs(:before_each_test) 190 | Puppet::Test::TestHelper.stubs(:after_each_test) 191 | Puppet.stubs(:[]=) 192 | Puppet::Node.stubs(:new).returns(the_node) 193 | Puppet::Resource::Catalog.stubs(:indirection).returns(the_indirection) 194 | File.stubs(:read).returns(:stub_file) 195 | subject.stubs(:get_modulepath).returns(:the_modulepath) 196 | subject.stubs(:link_module) 197 | end 198 | 199 | it "should initialize Puppet" do 200 | Puppet::Test::TestHelper.expects(:before_each_test) 201 | subject.catalog(:stub_path) 202 | end 203 | 204 | it "should read the spec manifest" do 205 | File.expects(:read).with(:stub_path) 206 | subject.catalog(:stub_path) 207 | end 208 | 209 | it "should give Puppet the spec manifest" do 210 | Puppet.expects(:[]=).with(:code, :stub_file) 211 | subject.catalog(:stub_path) 212 | end 213 | 214 | it "should calculate the modulepath" do 215 | subject.expects(:get_modulepath).with(the_node) 216 | subject.catalog(:stub_path) 217 | end 218 | 219 | it "should create the module symlink" do 220 | subject.expects(:link_module) 221 | subject.catalog(:stub_path) 222 | end 223 | 224 | it "should compile the catalog" do 225 | the_indirection.expects(:find).with(:stub_name, :use_node => the_node).returns(the_catalog) 226 | subject.catalog(:stub_path) 227 | end 228 | 229 | it "should finalize the catalog" do 230 | the_catalog.expects(:to_ral) 231 | subject.catalog(:stub_path) 232 | end 233 | 234 | it "should clean up the test" do 235 | Puppet::Test::TestHelper.expects(:after_each_test) 236 | subject.catalog(:stub_path) 237 | end 238 | 239 | it "should return the catalog" do 240 | expect(subject.catalog(:stub_path)).to eq(the_catalog) 241 | end 242 | end 243 | 244 | describe ".get_modulepath" do 245 | context "when given a node object" do 246 | let(:the_environment) { stub('environment', :full_modulepath => [:stub_path, 2]) } 247 | let(:the_node) { stub('node', :environment => the_environment) } 248 | 249 | it "should return the correct modulepath" do 250 | expect(subject.get_modulepath(the_node)).to eq(:stub_path) 251 | end 252 | end 253 | end 254 | 255 | describe ".link_module" do 256 | before do 257 | Dir.stubs(:pwd).returns(:stub_pwd) 258 | Dir.stubs(:exist?) 259 | File.stubs(:symlink?) 260 | File.stubs(:basename).returns(:stub_name) 261 | File.stubs(:join).returns(:stub_sympath) 262 | FileUtils.stubs(:mkdir_p) 263 | FileUtils.stubs(:ln_s) 264 | end 265 | 266 | it "should get the module's directory" do 267 | Dir.expects(:pwd).returns(:stub_pwd) 268 | subject.link_module(:stub_module) 269 | end 270 | 271 | it "should get the module's name" do 272 | File.expects(:basename).with(:stub_pwd).returns(:stub_name) 273 | subject.link_module(:stub_module) 274 | end 275 | 276 | it "should get the symlink's path" do 277 | File.expects(:join).with(:stub_module, :stub_name).returns(:stub_sympath) 278 | subject.link_module(:stub_module) 279 | end 280 | 281 | it "should check if the modulepath exists" do 282 | Dir.expects(:exist?).with(:stub_module) 283 | subject.link_module(:stub_module) 284 | end 285 | 286 | context "when the modulepath exists" do 287 | before { Dir.stubs(:exist?).returns(true) } 288 | it "should not create the modulepath directory" do 289 | FileUtils.expects(:mkdir_p).never 290 | subject.link_module(:stub_module) 291 | end 292 | end 293 | 294 | context "when the modulepath does not exist" do 295 | before { Dir.stubs(:exist?).returns(false) } 296 | it "should create the modulepath directory" do 297 | FileUtils.expects(:mkdir_p).with(:stub_module) 298 | subject.link_module(:stub_module) 299 | end 300 | end 301 | 302 | it "should check if the symlink exists" do 303 | File.expects(:symlink?).with(:stub_sympath) 304 | subject.link_module(:stub_module) 305 | end 306 | 307 | context "when the symlink does exist" do 308 | before { File.stubs(:symlink?).returns(true) } 309 | it "should not create the symlink" do 310 | FileUtils.expects(:ln_s).never 311 | subject.link_module(:stub_module) 312 | end 313 | end 314 | 315 | context "when the symlink does not exist" do 316 | before { File.stubs(:symlink?).returns(false) } 317 | it "should not create the symlink" do 318 | FileUtils.expects(:ln_s).with(:stub_pwd, :stub_sympath) 319 | subject.link_module(:stub_module) 320 | end 321 | end 322 | end 323 | 324 | describe ".specdir" do 325 | before do 326 | Dir.stubs(:pwd).returns(:stub_pwd) 327 | Dir.stubs(:exist?).returns(true) 328 | File.stubs(:join).returns(:stub_specdir) 329 | end 330 | 331 | it "should get the pwd" do 332 | Dir.expects(:pwd).returns(:stub_pwd) 333 | subject.specdir 334 | end 335 | 336 | it "should parse the specdir" do 337 | File.expects(:join).with(:stub_pwd, 'spec').returns(:stub_specdir) 338 | subject.specdir 339 | end 340 | 341 | context "when the CWD contains a spec directory" do 342 | before { Dir.stubs(:exist?).returns(true) } 343 | 344 | it "should return the path to the specdir" do 345 | expect(subject.specdir).to eq(:stub_specdir) 346 | end 347 | 348 | it "should return the specdir" do 349 | expect(subject.specdir).to eq(:stub_specdir) 350 | end 351 | end 352 | 353 | context "when the CWD does not contain a spec directory" do 354 | before { Dir.stubs(:exist?).returns(false) } 355 | 356 | it "should raise an error" do 357 | expect{subject.specdir}.to raise_error( 358 | 'No spec directory was found under the CWD. A spec manifest can be specified with the --manifest flag' 359 | ) 360 | end 361 | end 362 | end 363 | 364 | end 365 | --------------------------------------------------------------------------------