1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
|
#
# 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.register(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
|