Skip to content
Browse files

Initial rake support.

  • Loading branch information...
1 parent bdf379a commit 094ec2a14ec78da477b68172329969cb4a32c025 @acrmp committed Jun 17, 2012
View
7 Rakefile
@@ -14,11 +14,12 @@ Rake::TestTask.new do |t|
end
Cucumber::Rake::Task.new(:features) do |t|
- t.cucumber_opts = ['-f', 'progress', '-t']
+ t.cucumber_opts = ['-f', 'progress']
if ENV.has_key?('FC_FORK_PROCESS') and ENV['FC_FORK_PROCESS'] == true.to_s
- t.cucumber_opts += ['~@repl']
+ t.cucumber_opts += ['-t', '~@repl']
else
- t.cucumber_opts += ['~@context']
+ t.cucumber_opts += ['-t', '~@build']
+ t.cucumber_opts += ['-t', '~@context']
end
t.cucumber_opts += ['features']
end
View
90 features/build_framework_support.feature
@@ -0,0 +1,90 @@
+@build
+Feature: Build framework support
+
+In order to make it as easy as possible to lint my cookbook
+As a developer
+I want to be able to invoke the lint tool from my build
+
+ Scenario: List rake tasks
+ Given a cookbook that has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task with no block
+ When I list the available build tasks
+ Then the lint task will be listed
+
+ Scenario: List rake tasks - modified name
+ Given a cookbook that has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task specifying a different name
+ When I list the available build tasks
+ Then the lint task will be listed under the different name
+
+ Scenario Outline: Rakefile with no lint task
+ Given a cookbook that has <problems> problems
+ And the cookbook has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines no lint task
+ When I run the build
+ Then the build will <build_outcome> with no warnings
+ Examples:
+ | problems | build_outcome |
+ | no | succeed |
+ | style | succeed |
+
+ Scenario Outline: Lint task with no block
+ Given a cookbook that has <problems> problems
+ And the cookbook has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task with no block
+ When I run the build
+ Then the build will <build_outcome> with warnings <warnings>
+ Examples:
+ | problems | build_outcome | warnings |
+ | no | succeed | |
+ | style | succeed | FC002 |
+ | correctness | fail | FC006 |
+ | style,correctness | fail | FC002,FC006 |
+
+ Scenario Outline: Lint task with empty block
+ Given a cookbook that has <problems> problems
+ And the cookbook has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task with an empty block
+ When I run the build
+ Then the build will <build_outcome> with warnings <warnings>
+ Examples:
+ | problems | build_outcome | warnings |
+ | no | succeed | |
+ | style | succeed | FC002 |
+ | style,correctness | fail | FC002,FC006 |
+
+ Scenario Outline: Specify rule tags to fail on
+ Given a cookbook that has <problems> problems
+ And the cookbook has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task with a block setting options to <options>
+ When I run the build
+ Then the build will <build_outcome> with warnings <warnings>
+ Examples:
+ | problems | options | build_outcome | warnings |
+ | no | | succeed | |
+ | style,correctness | {:fail_tags => []} | succeed | FC002,FC006 |
+ | correctness | {:fail_tags => ['correctness']} | fail | FC006 |
+ | style | {:fail_tags => ['correctness,style']} | fail | FC002 |
+ | style,correctness | {:fail_tags => [], :tags => ['correctness']} | succeed | FC006 |
+
+ Scenario Outline: Specify paths to lint
+ Given a cookbook that has <problems> problems
+ And the cookbook has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task specifying files to lint as <files>
+ When I run the build
+ Then the build will <build_outcome> with warnings <warnings>
+ Examples:
+ | problems | files | build_outcome | warnings |
+ | no | ['recipes/default.rb'] | succeed | |
+ | correctness | ['recipes/default.rb'] | fail | FC006 |
+ | no | ['recipes/default.rb', 'recipes/server.rb'] | succeed | |
+ | correctness | ['recipes/default.rb', 'recipes/server.rb'] | fail | FC006 |
+
+ Scenario: Exclude tests
+ Given a cookbook that has style problems
+ And the cookbook has a Gemfile that includes rake and foodcritic
+ And a Rakefile that defines a lint task with no block
+ And unit tests under a top-level test directory
+ When I run the build
+ Then no warnings will be displayed against the tests
+ And the build will succeed with warnings FC002
View
63 features/step_definitions/cookbook_steps.rb
@@ -507,6 +507,18 @@
'a users home directory' => :home_dir}[path])
end
+Given /^a cookbook that has ([^ ]+) problems$/ do |problems|
+ cookbook_that_matches_rules(
+ problems.split(',').map do |problem|
+ case problem
+ when 'no ' then next
+ when 'style' then 'FC002'
+ when 'correctness' then 'FC006'
+ end
+ end
+ )
+end
+
Given 'a cookbook that has a README in markdown format' do
write_recipe %q{
log "Hello"
@@ -722,6 +734,24 @@ def in_tier?(*tier)
}
end
+Given /^a Rakefile that defines (no lint task|a lint task with no block|a lint task with an empty block|a lint task with a block setting options to)(.*)?$/ do |task,options|
+ rakefile(
+ case task
+ when /no block/ then :no_block
+ when /empty block/ then :empty_block
+ when /a block/ then :block
+ end,
+ options.strip.empty? ? {} : {:options => options.strip})
+end
+
+Given /^a Rakefile that defines a lint task specifying files to lint as (.*)$/ do |files|
+ rakefile(:block, :files => files)
+end
+
+Given 'a Rakefile that defines a lint task specifying a different name' do
+ rakefile(:block, :name => 'lint')
+end
+
Given /^a recipe that declares a ([^ ]+) resource with these attributes: (.*)$/ do |type,attributes|
recipe_with_resource(type, attributes.split(','))
end
@@ -836,10 +866,18 @@ def search(bag_name, query=nil, sort=nil, start=0, rows=1000, &block)
}
end
+Given /^(?:a cookbook that has|the cookbook has) a Gemfile that includes rake and foodcritic$/ do
+ buildable_gemfile
+end
+
Given /^the current stable version of Chef (falls|does not fall) within it$/ do |falls_within|
rule_with_version_constraint("98.10", nil) unless falls_within.include?('falls')
end
+Given 'unit tests under a top-level test directory' do
+ minitest_spec_attributes
+end
+
When /^I check the cookbook specifying ([^ ]+) as the Chef version$/ do |version|
options = ['-c', version, 'cookbooks/example']
in_current_dir do
@@ -874,6 +912,10 @@ def search(bag_name, query=nil, sort=nil, start=0, rows=1000, &block)
:rule_match_string => @repl_match_string)
end
+When 'I list the available build tasks' do
+ list_available_build_tasks
+end
+
When /^I run it on the command line including a custom rule (file|directory) containing a rule that matches$/ do |path_type|
write_file 'rules/custom_rules.rb', %q{
rule "BAR001", "Use symbols in preference to strings to access node attributes" do
@@ -917,6 +959,10 @@ def search(bag_name, query=nil, sort=nil, start=0, rows=1000, &block)
run_lint(['-v'])
end
+When 'I run the build' do
+ run_build
+end
+
Then 'a warning for the custom rule should be displayed' do
expect_output('BAR001: Use symbols in preference to strings to access node attributes: cookbooks/example/recipes/default.rb:1')
end
@@ -960,10 +1006,19 @@ def search(bag_name, query=nil, sort=nil, start=0, rows=1000, &block)
]
end
+Then /^the lint task will be listed( under the different name)?$/ do |diff_name|
+ expected_name = diff_name ? 'lint' : 'foodcritic'
+ build_tasks.should include([expected_name, 'Lint Chef cookbooks'])
+end
+
Then 'no error should have occurred' do
assert_no_error_occurred
end
+Then /^no warnings will be displayed against the tests$/ do
+ assert_no_test_warnings
+end
+
Then /^the warning should (not )?be displayed$/ do |should_not|
expect_warning 'FCTEST001', {:expect_warning => should_not.nil?}
end
@@ -996,8 +1051,12 @@ def search(bag_name, query=nil, sort=nil, start=0, rows=1000, &block)
end
end
-Then /the build status should be (successful|failed)$/ do |build_status|
- build_status == 'successful' ? assert_no_error_occurred : assert_error_occurred
+Then /the build status should be (successful|failed)$/ do |build_outcome|
+ build_outcome == 'successful' ? assert_no_error_occurred : assert_error_occurred
+end
+
+Then /^the build will (succeed|fail) with (?:no )?warnings(.*)$/ do |build_outcome, warnings|
+ assert_build_result(build_outcome == 'succeed', warnings.gsub(' ', '').split(','))
end
Then 'the check for server warning 003 should not be displayed given we have checked' do
View
50 features/support/command_helpers.rb
@@ -47,6 +47,35 @@ def self.running_in_process?
! (ENV.has_key?('FC_FORK_PROCESS') and ENV['FC_FORK_PROCESS'] == true.to_s)
end
+ # Assert the build outcome
+ #
+ # @param [Boolean] success True if the build should succeed
+ # @param [Array] warnings The warnings expected
+ def assert_build_result(success, warnings)
+ success ? assert_no_error_occurred : assert_error_occurred
+ warnings.each do |code|
+ expect_warning(code, :warning_only => true)
+ end
+ end
+
+ # Assert that warnings have not been raised against the test code which
+ # should have been excluded from linting.
+ def assert_no_test_warnings
+ all_output.split("\n").grep(/FC[0-9]+:/).map do |warn|
+ File.basename(File.dirname(warn.split(':').take(3).last.strip))
+ end.should_not include 'test'
+ end
+
+ # The available tasks for this build
+ #
+ # @return [Array] Task name and description
+ def build_tasks
+ all_output.split("\n").map do |task|
+ next unless task.start_with? 'rake'
+ task.split("#").map{|t| t.strip.sub(/^rake /, '')}
+ end.compact
+ end
+
# Capture an error expected when calling a command.
def capture_error
begin
@@ -112,6 +141,27 @@ def expect_usage_option(short_switch, long_switch, description)
expect_output(Regexp.new(expected_switch))
end
+ # List the defined Rake tasks
+ def list_available_build_tasks
+ cd 'cookbooks/example'
+ run "bundle exec rake -T"
+ end
+
+ # Create a placeholder minitest spec that would be linted due to its path
+ # unless an exclusion is specified.
+ def minitest_spec_attributes
+ write_file 'cookbooks/example/test/attributes/default_spec.rb', %q{
+ describe 'Example::Attributes::Default' do
+ end
+ }
+ end
+
+ # Run a build for a Rakefile that uses the lint rake task
+ def run_build
+ cd 'cookbooks/example'
+ run 'bundle exec rake'
+ end
+
# Assert that the usage message is displayed.
#
# @param [Boolean] is_exit_zero The exit code to check for.
View
45 features/support/cookbook_helpers.rb
@@ -10,6 +10,14 @@ def attributes_with_symbols(type)
write_attributes %Q{#{type}[:apache][:dir] = "/etc/apache2"}
end
+ # Create a Gemfile for a cookbook
+ def buildable_gemfile
+ write_file 'cookbooks/example/Gemfile', %q{
+ gem 'rake'
+ gem 'foodcritic', :path => '../../../..'
+ }
+ end
+
# Create a cookbook that declares dependencies on external recipes.
#
# @param [Symbol] declaration_type The type of declaration - :brace or :bracket
@@ -53,9 +61,17 @@ def cookbook_that_matches_rules(codes)
action :run
end
}
+ elsif code == 'FC006'
+ recipe += %q{
+ directory "/var/lib/foo" do
+ mode 644
+ action :create
+ end
+ }
end
end
write_recipe(recipe)
+ write_file('cookbooks/example/recipes/server.rb', '')
write_readme('Hello World') # Don't trigger FC011
write_metadata('name "example"') # Don't trigger FC031
end
@@ -118,6 +134,35 @@ def cookbook_with_maintainer(name, email)
}
end
+ # Create a Rakefile that uses the linter rake task
+ #
+ # @param [Symbol] task Type of task
+ # @param [Hash] options Task options
+ # @option options [String] :name Task name
+ # @option options [String] :files Files to process
+ # @option options [String] :options The options to set on the rake task
+ def rakefile(task, options)
+ rakefile_content = 'task :default => []'
+ task_def = case task
+ when :no_block then 'FoodCritic::Rake::LintTask.new'
+ else %Q{
+ FoodCritic::Rake::LintTask.new do |t|
+ #{"t.name = '#{options[:name]}'" if options[:name]}
+ #{"t.files = #{options[:files]}" if options[:files]}
+ #{"t.options = #{options[:options]}" if options[:options]}
+ end
+ }
+ end
+ if task_def
+ rakefile_content = %Q{
+ require 'foodcritic'
+ task :default => [:#{options[:name] ? options[:name] : 'foodcritic'}]
+ #{task_def}
+ }
+ end
+ write_file 'cookbooks/example/Rakefile', rakefile_content
+ end
+
# Create a recipe that downloads a file
#
# @param [Symbol] path_type The type of path, one of: :tmp_dir, :chef_file_cache_dir, :home_dir
View
1 lib/foodcritic.rb
@@ -14,4 +14,5 @@
require_relative 'foodcritic/dsl'
require_relative 'foodcritic/linter'
require_relative 'foodcritic/output'
+require_relative 'foodcritic/rake_task'
require_relative 'foodcritic/version'
View
15 lib/foodcritic/linter.rb
@@ -58,7 +58,7 @@ def check(cookbook_paths, options)
end
options = {:tags => [], :fail_tags => [],
- :include_rules => []}.merge(options)
+ :include_rules => [], :exclude_paths => []}.merge(options)
@last_cookbook_paths, @last_options = cookbook_paths, options
load_rules unless defined? @rules
@@ -68,7 +68,7 @@ def check(cookbook_paths, options)
matching_tags?(options[:tags], rule.tags) and
applies_to_version?(rule, options[:chef_version] || DEFAULT_CHEF_VERSION)
end
- files_to_process(cookbook_paths).each do |file|
+ files_to_process(cookbook_paths, options[:exclude_paths]).each do |file|
cookbook_dir = Pathname.new(
File.join(File.dirname(file), '..')).cleanpath
ast = read_ast(file)
@@ -158,19 +158,18 @@ def matches(match_method, *params)
# @param [Array<String>] dirs The cookbook path(s)
# @return [Array] The files underneath the provided paths to be
# processed.
- def files_to_process(dirs)
+ def files_to_process(dirs, exclude_paths = [])
files = []
-
dirs.each do |dir|
+ exclusions = Dir.glob(exclude_paths.map{|p| File.join(dir, p)})
if File.directory? dir
cookbook_glob = '{attributes,providers,recipes,resources}/*.rb'
- files += Dir.glob(File.join(dir, cookbook_glob)) +
- Dir.glob(File.join(dir, "*/#{cookbook_glob}"))
+ files += (Dir.glob(File.join(dir, cookbook_glob)) +
+ Dir.glob(File.join(dir, "*/#{cookbook_glob}")) - exclusions)
else
- files << dir
+ files << dir unless exclusions.include?(dir)
end
end
-
files
end
View
34 lib/foodcritic/rake_task.rb
@@ -0,0 +1,34 @@
+require 'rake'
+require 'rake/tasklib'
+
+module FoodCritic
+ module Rake
+ class LintTask < ::Rake::TaskLib
+ attr_accessor :name, :files
+ attr_writer :options
+
+ def initialize(name = :foodcritic)
+ @name = name
+ @files = Dir.pwd
+ @options = {}
+ yield self if block_given?
+ define
+ end
+
+ def options
+ {:fail_tags => ['correctness'], # differs to default cmd-line behaviour
+ :exclude_paths => ['test/**/*']}.merge(@options)
+ end
+
+ def define
+ desc "Lint Chef cookbooks"
+ task(name) do
+ result = FoodCritic::Linter.new.check(files, options)
+ puts result
+ fail if result.failed?
+ end
+ end
+
+ end
+ end
+end

0 comments on commit 094ec2a

Please sign in to comment.
Something went wrong with that request. Please try again.