File Manager
Viewing File: runner.rb
#
# Author:: Claire McQuin (<claire@chef.io>)
# Copyright:: Copyright 2014-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require "chef/audit/logger"
class Chef
class Audit
class Runner
attr_reader :run_context
private :run_context
def initialize(run_context)
@run_context = run_context
end
def run
setup
register_control_groups
do_run
end
def failed?
RSpec.world.reporter.failed_examples.size > 0
end
def num_failed
RSpec.world.reporter.failed_examples.size
end
def num_total
RSpec.world.reporter.examples.size
end
def exclusion_pattern
Regexp.new(".+[\\\/]lib[\\\/]chef[\\\/]")
end
private
# Prepare to run audits:
# - Require files
# - Configure RSpec
# - Configure Specinfra/Serverspec
def setup
require_deps
configure_rspec
configure_specinfra
end
# RSpec uses a global configuration object, RSpec.configuration. We found
# there was interference between the configuration for audit-mode and
# the configuration for our own spec tests in these cases:
# 1. Specinfra and Serverspec modify RSpec.configuration when loading.
# 2. Setting output/error streams.
# 3. Adding formatters.
# 4. Defining example group aliases.
#
# Moreover, Serverspec loads its DSL methods into the global namespace,
# which causes conflicts with the Chef namespace for resources and packages.
#
# We wait until we're in the audit-phase of the chef-client run to load
# these files. This helps with the namespacing problems we saw, and
# prevents Specinfra and Serverspec from modifying the RSpec configuration
# used by our spec tests.
def require_deps
require "rspec"
require "rspec/its"
require "specinfra"
require "specinfra/helper"
require "specinfra/helper/set"
require "serverspec/helper"
require "serverspec/matcher"
require "serverspec/subject"
require "chef/audit/audit_event_proxy"
require "chef/audit/rspec_formatter"
Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set)
end
# Configure RSpec just the way we like it:
# - Set location of error and output streams
# - Add custom audit-mode formatters
# - Explicitly disable :should syntax
# - Set :color option according to chef config
# - Disable exposure of global DSL
def configure_rspec
set_streams
add_formatters
disable_should_syntax
RSpec.configure do |c|
c.color = Chef::Config[:color]
c.expose_dsl_globally = false
c.project_source_dirs = Array(Chef::Config[:cookbook_path])
c.backtrace_exclusion_patterns << exclusion_pattern
end
end
# Set the error and output streams which audit-mode will use to report
# human-readable audit information.
#
# This should always be called before #add_formatters. RSpec won't allow
# the output stream to be changed for a formatter once the formatter has
# been added.
def set_streams
RSpec.configuration.output_stream = Chef::Audit::Logger
RSpec.configuration.error_stream = Chef::Audit::Logger
end
# Add formatters which we use to
# 1. Output human-readable data to the output stream,
# 2. Collect JSON data to send back to the analytics server.
def add_formatters
RSpec.configuration.add_formatter(Chef::Audit::AuditEventProxy)
RSpec.configuration.add_formatter(Chef::Audit::RspecFormatter)
Chef::Audit::AuditEventProxy.events = run_context.events
end
# Audit-mode uses RSpec 3. :should syntax is deprecated by default in
# RSpec 3, so we explicitly disable it here.
#
# This can be removed once :should is removed from RSpec.
def disable_should_syntax
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :expect
end
end
end
# Set up the backend for Specinfra/Serverspec. :exec is the local system; on Windows, it is :cmd
def configure_specinfra
if Chef::Platform.windows?
Specinfra.configuration.backend = :cmd
Specinfra.configuration.os = { :family => "windows" }
else
Specinfra.configuration.backend = :exec
end
end
# Iterates through the control groups registered to this run_context, builds an
# example group (RSpec::Core::ExampleGroup) object per control group, and
# registers the group with the RSpec.world.
#
# We could just store an array of example groups and not use RSpec.world,
# but it may be useful later if we decide to apply our own ordering scheme
# or use example group filters.
def register_control_groups
add_example_group_methods
run_context.audits.each do |name, group|
ctl_grp = RSpec::Core::ExampleGroup.__control_group__(*group.args, &group.block)
RSpec.world.record(ctl_grp)
end
end
# Add example group method aliases to RSpec.
#
# __control_group__: Used internally to create example groups from the control
# groups saved in the run_context.
# control: Used within the context of a control group block, like RSpec's
# describe or context.
def add_example_group_methods
RSpec::Core::ExampleGroup.define_example_group_method :__control_group__
RSpec::Core::ExampleGroup.define_example_group_method :control
end
# Run the audits!
def do_run
# RSpec::Core::Runner wants to be initialized with an
# RSpec::Core::ConfigurationOptions object, which is used to process
# command line configuration arguments. We directly fiddle with the
# internal RSpec configuration object, so we give nil here and let
# RSpec pick up its own configuration and world.
runner = RSpec::Core::Runner.new(nil)
runner.run_specs(RSpec.world.ordered_example_groups)
end
end
end
end