Land #4332, test optimization for Cucumber

* Make Cuke run faster on TravisCI
bug/bundler_fix
Trevor Rosen 2014-12-18 09:34:55 -06:00
commit 80cd04d76a
No known key found for this signature in database
GPG Key ID: 255ADB7A642D3928
13 changed files with 86 additions and 69 deletions

View File

@ -1,14 +1,14 @@
bundler_args: --without development coverage
env: env:
- RAKE_TASK=cucumber - RAKE_TASKS="cucumber cucumber:boot"
# Commenting out the boot tests due to chronic timeouts. - RAKE_TASKS=spec SPEC_OPTS="--tag content"
# - RAKE_TASK=cucumber:boot - RAKE_TASKS=spec SPEC_OPTS="--tag ~content"
- RAKE_TASK=spec SPEC_OPTS="--tag content"
- RAKE_TASK=spec SPEC_OPTS="--tag ~content"
language: ruby language: ruby
matrix: matrix:
fast_finish: true fast_finish: true
before_install: before_install:
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
- rake --version - rake --version
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -qq libpcap-dev - sudo apt-get install -qq libpcap-dev
@ -25,7 +25,7 @@ before_script:
- bundle exec rake db:migrate - bundle exec rake db:migrate
script: script:
# fail build if db/schema.rb update is not committed # fail build if db/schema.rb update is not committed
- git diff --exit-code && bundle exec rake $RAKE_TASK - git diff --exit-code && bundle exec rake $RAKE_TASKS
rvm: rvm:
- '1.9.3' - '1.9.3'

12
Gemfile
View File

@ -3,6 +3,14 @@ source 'https://rubygems.org'
# spec.add_runtime_dependency '<name>', [<version requirements>] # spec.add_runtime_dependency '<name>', [<version requirements>]
gemspec name: 'metasploit-framework' gemspec name: 'metasploit-framework'
# separate from test as simplecov is not run on travis-ci
group :coverage do
# code coverage for tests
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
# see: https://github.com/colszowka/simplecov/issues/127 (hopefully fixed in 0.8.0)
gem 'simplecov', '0.5.4', :require => false
end
group :db do group :db do
gemspec name: 'metasploit-framework-db' gemspec name: 'metasploit-framework-db'
end end
@ -44,10 +52,6 @@ group :test do
# cucumber + automatic database cleaning with database_cleaner # cucumber + automatic database cleaning with database_cleaner
gem 'cucumber-rails', :require => false gem 'cucumber-rails', :require => false
gem 'shoulda-matchers' gem 'shoulda-matchers'
# code coverage for tests
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
# see: https://github.com/colszowka/simplecov/issues/127 (hopefully fixed in 0.8.0)
gem 'simplecov', '0.5.4', :require => false
# Manipulate Time.now in specs # Manipulate Time.now in specs
gem 'timecop' gem 'timecop'
end end

View File

@ -9,6 +9,7 @@ all_environments = [
Bundler.require( Bundler.require(
*Rails.groups( *Rails.groups(
coverage: [:test],
db: all_environments, db: all_environments,
pcap: all_environments pcap: all_environments
) )

View File

@ -1,12 +1,9 @@
Feature: Help command Feature: Help command
Background: Background:
Given I run `msfconsole` interactively Given I run `msfconsole --defer-module-loads -x help -x exit`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
Scenario: The 'help' command's output Scenario: The 'help' command's output
When I type "help"
And I type "exit"
Then the output should contain: Then the output should contain:
""" """
Core Commands Core Commands

View File

@ -48,9 +48,7 @@ Feature: `msfconsole` `database.yml`
database: project_metasploit_framework_test database: project_metasploit_framework_test
username: project_metasploit_framework_test username: project_metasploit_framework_test
""" """
When I run `msfconsole --environment test --yaml command_line.yml` interactively When I run `msfconsole --defer-module-loads --environment test --execute-command exit --yaml command_line.yml`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
And I type "exit"
Then the output should contain "command_line_metasploit_framework_test" Then the output should contain "command_line_metasploit_framework_test"
Scenario: Without --yaml, MSF_DATABASE_CONFIG wins Scenario: Without --yaml, MSF_DATABASE_CONFIG wins
@ -84,9 +82,7 @@ Feature: `msfconsole` `database.yml`
database: project_metasploit_framework_test database: project_metasploit_framework_test
username: project_metasploit_framework_test username: project_metasploit_framework_test
""" """
When I run `msfconsole --environment test` interactively When I run `msfconsole --defer-module-loads --environment test --execute-command exit`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
And I type "exit"
Then the output should contain "environment_metasploit_framework_test" Then the output should contain "environment_metasploit_framework_test"
Scenario: Without --yaml or MSF_DATABASE_CONFIG, ~/.msf4/database.yml wins Scenario: Without --yaml or MSF_DATABASE_CONFIG, ~/.msf4/database.yml wins
@ -113,9 +109,7 @@ Feature: `msfconsole` `database.yml`
database: project_metasploit_framework_test database: project_metasploit_framework_test
username: project_metasploit_framework_test username: project_metasploit_framework_test
""" """
When I run `msfconsole --environment test` interactively When I run `msfconsole --defer-module-loads --environment test --execute-command exit`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
And I type "exit"
Then the output should contain "user_metasploit_framework_test" Then the output should contain "user_metasploit_framework_test"
Scenario: Without --yaml, MSF_DATABASE_CONFIG or ~/.msf4/database.yml, project "database.yml" wins Scenario: Without --yaml, MSF_DATABASE_CONFIG or ~/.msf4/database.yml, project "database.yml" wins
@ -133,9 +127,7 @@ Feature: `msfconsole` `database.yml`
database: project_metasploit_framework_test database: project_metasploit_framework_test
username: project_metasploit_framework_test username: project_metasploit_framework_test
""" """
When I run `msfconsole --environment test` interactively When I run `msfconsole --defer-module-loads --environment test --execute-command exit`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
And I type "exit"
Then the output should contain "project_metasploit_framework_test" Then the output should contain "project_metasploit_framework_test"
@ -148,10 +140,7 @@ Feature: `msfconsole` `database.yml`
And a mocked home directory And a mocked home directory
And I cd to "../.." And I cd to "../.."
And the project "database.yml" does not exist And the project "database.yml" does not exist
When I run `msfconsole --environment test` interactively When I run `msfconsole --defer-module-loads --environment test --execute-command db_status --execute-command exit`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
And I type "db_status"
And I type "exit"
Then the output should not contain "command_line_metasploit_framework_test" Then the output should not contain "command_line_metasploit_framework_test"
And the output should not contain "environment_metasploit_framework_test" And the output should not contain "environment_metasploit_framework_test"
And the output should not contain "user_metasploit_framework_test" And the output should not contain "user_metasploit_framework_test"
@ -159,9 +148,6 @@ Feature: `msfconsole` `database.yml`
And the output should contain "[*] postgresql selected, no connection" And the output should contain "[*] postgresql selected, no connection"
Scenario: Starting `msfconsole` with a valid database.yml Scenario: Starting `msfconsole` with a valid database.yml
Given I run `msfconsole` interactively When I run `msfconsole --defer-module-loads --execute-command db_status --execute-command exit`
And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
When I type "db_status"
And I type "exit"
Then the output should contain "[*] postgresql connected to metasploit_framework_test" Then the output should contain "[*] postgresql connected to metasploit_framework_test"

View File

@ -1,5 +1,5 @@
# Has to be the first file required so that all other files show coverage information # @note `require 'simplecov'` is not used here because all features currently use external `msfconsole` process, so only
require 'simplecov' # that child process needs to load 'simplecov'.
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a # It is recommended to regenerate this file in the future when you upgrade to a

View File

@ -4,23 +4,26 @@ Before do
@aruba_timeout_seconds = 8.minutes @aruba_timeout_seconds = 8.minutes
end end
Before do |scenario| # don't setup child processes to load simplecov_setup.rb if simplecov isn't installed
command_name = case scenario unless Bundler.settings.without.include?(:coverage)
when Cucumber::Ast::Scenario, Cucumber::Ast::ScenarioOutline Before do |scenario|
"#{scenario.feature.title} #{scenario.name}" command_name = case scenario
when Cucumber::Ast::OutlineTable::ExampleRow when Cucumber::Ast::Scenario, Cucumber::Ast::ScenarioOutline
scenario_outline = scenario.scenario_outline "#{scenario.feature.title} #{scenario.name}"
when Cucumber::Ast::OutlineTable::ExampleRow
scenario_outline = scenario.scenario_outline
"#{scenario_outline.feature.title} #{scenario_outline.name} #{scenario.name}" "#{scenario_outline.feature.title} #{scenario_outline.name} #{scenario.name}"
else else
raise TypeError, "Don't know how to extract command name from #{scenario.class}" raise TypeError, "Don't know how to extract command name from #{scenario.class}"
end end
# Used in simplecov_setup so that each scenario has a different name and their coverage results are merged instead # Used in simplecov_setup so that each scenario has a different name and their coverage results are merged instead
# of overwriting each other as 'Cucumber Features' # of overwriting each other as 'Cucumber Features'
set_env('SIMPLECOV_COMMAND_NAME', command_name) set_env('SIMPLECOV_COMMAND_NAME', command_name)
simplecov_setup_pathname = Pathname.new(__FILE__).expand_path.parent.join('simplecov_setup') simplecov_setup_pathname = Pathname.new(__FILE__).expand_path.parent.join('simplecov_setup')
# set environment variable so child processes will merge their coverage data with parent process's coverage data. # set environment variable so child processes will merge their coverage data with parent process's coverage data.
set_env('RUBYOPT', "-r#{simplecov_setup_pathname} #{ENV['RUBYOPT']}") set_env('RUBYOPT', "#{ENV['RUBYOPT']} -r#{simplecov_setup_pathname}")
end
end end

View File

@ -1,11 +1,16 @@
# @note this file is loaded in env.rb to setup simplecov using RUBYOPTs for child processes # @note this file is loaded in env.rb to setup simplecov using RUBYOPTs for child processes
require 'simplecov' simplecov_command_name = ENV['SIMPLECOV_COMMAND_NAME']
require 'pathname' # will not be set if hook does not run because `bundle install --without coverage`
if simplecov_command_name
require 'simplecov'
root = Pathname(__FILE__).expand_path.parent.parent.parent require 'pathname'
SimpleCov.command_name(ENV['SIMPLECOV_COMMAND_NAME']) root = Pathname(__FILE__).expand_path.parent.parent.parent
SimpleCov.root(root)
load root.join('.simplecov') SimpleCov.command_name(simplecov_command_name)
SimpleCov.root(root)
load root.join('.simplecov')
end

View File

@ -61,6 +61,7 @@ class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::
driver_options['DatabaseEnv'] = options.environment driver_options['DatabaseEnv'] = options.environment
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
driver_options['DatabaseYAML'] = options.database.config driver_options['DatabaseYAML'] = options.database.config
driver_options['DeferModuleLoads'] = options.modules.defer_loads
driver_options['Defanged'] = options.console.defanged driver_options['Defanged'] = options.console.defanged
driver_options['DisableBanner'] = options.console.quiet driver_options['DisableBanner'] = options.console.quiet
driver_options['DisableDatabase'] = options.database.disable driver_options['DisableDatabase'] = options.database.disable

View File

@ -85,6 +85,7 @@ class Metasploit::Framework::ParsedOptions::Base
options.framework.config = nil options.framework.config = nil
options.modules = ActiveSupport::OrderedOptions.new options.modules = ActiveSupport::OrderedOptions.new
options.modules.defer_loads = false
options.modules.path = nil options.modules.path = nil
@options = options @options = options
@ -155,6 +156,13 @@ class Metasploit::Framework::ParsedOptions::Base
option_parser.separator '' option_parser.separator ''
option_parser.separator 'Module options' option_parser.separator 'Module options'
option_parser.on(
'--defer-module-loads',
'Defer module loading unless explicitly asked.'
) do
options.modules.defer_loads = true
end
option_parser.on( option_parser.on(
'-m', '-m',
'--module-path DIRECTORY', '--module-path DIRECTORY',

View File

@ -62,21 +62,29 @@ module Framework
Msf::MODULE_POST => Msf::Simple::Post, Msf::MODULE_POST => Msf::Simple::Post,
} }
#
# Create a simplified instance of the framework. This routine takes a hash # Create a simplified instance of the framework. This routine takes a hash
# of parameters as an argument. This hash can contain: # of parameters as an argument. This hash can contain:
# #
# OnCreateProc => A callback procedure that is called once the framework # @param opts [Hash{String => Object}]
# instance is created. # @option opts (see simplify)
# # @return [Msf::Simple::Frameworkt s]
def self.create(opts = {}) def self.create(opts = {})
framework = Msf::Framework.new(opts) framework = Msf::Framework.new(opts)
return simplify(framework, opts) return simplify(framework, opts)
end end
# @note If `opts['ConfigDirectory']` is set, then `Msf::Config::Defaults['ConfigDirectory']` will be updated to
# `opts['ConfigDirectory']`.
# #
# Extends a framework object that may already exist. # Extends a framework object that may already exist.
# #
# @param framework [Msf::Framework, Msf::Simple::Framework] framework to simplify
# @param opts [Hash{String => Object}]
# @option opts [#call] 'OnCreateProc' Proc to call after {#init_simplified}. Will be passed `framework`.
# @option opts [String] 'ConfigDirectory' Directory where configuration is saved. The `~/.msf4` directory.
# @option opts [Boolean] 'DisableLogging' (false) `true` to disable `Msf::Logging.init`
# @option opts [Boolean] 'DeferModuleLoads' (false) `true` to disable `framework.init_module_paths`.
# @return [Msf::Simple::Framework] `framework`
def self.simplify(framework, opts) def self.simplify(framework, opts)
# If the framework instance has not already been extended, do it now. # If the framework instance has not already been extended, do it now.

View File

@ -61,7 +61,7 @@ class Driver < Msf::Ui::Driver
# Initialize attributes # Initialize attributes
# Defer loading of modules until paths from opts can be added below # Defer loading of modules until paths from opts can be added below
framework_create_options = {'DeferModuleLoads' => true}.merge(opts) framework_create_options = opts.merge('DeferModuleLoads' => true)
self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options) self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options)
if self.framework.datastore['Prompt'] if self.framework.datastore['Prompt']
@ -187,10 +187,12 @@ class Driver < Msf::Ui::Driver
# framework.db.active will be true if after_establish_connection ran directly when connection_established? was # framework.db.active will be true if after_establish_connection ran directly when connection_established? was
# already true or if framework.db.connect called after_establish_connection. # already true or if framework.db.connect called after_establish_connection.
if framework.db.active if framework.db.active
self.framework.modules.refresh_cache_from_database unless opts['DeferModuleLoads']
self.framework.modules.refresh_cache_from_database
if self.framework.modules.cache_empty? if self.framework.modules.cache_empty?
print_status("The initial module cache will be built in the background, this can take 2-5 minutes...") print_status("The initial module cache will be built in the background, this can take 2-5 minutes...")
end
end end
elsif !framework.db.error.nil? elsif !framework.db.error.nil?
if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i
@ -212,8 +214,8 @@ class Driver < Msf::Ui::Driver
end end
end end
# Initialize the module paths only if we didn't get passed a Framework instance # Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false
unless opts['Framework'] unless opts['Framework'] || opts['DeferModuleLoads']
# Configure the framework module paths # Configure the framework module paths
self.framework.init_module_paths self.framework.init_module_paths
self.framework.modules.add_module_path(opts['ModulePath']) if opts['ModulePath'] self.framework.modules.add_module_path(opts['ModulePath']) if opts['ModulePath']

View File

@ -1,7 +1,9 @@
# -*- coding: binary -*- # -*- coding: binary -*-
ENV['RAILS_ENV'] = 'test' ENV['RAILS_ENV'] = 'test'
require 'simplecov' unless Bundler.settings.without.include?(:coverage)
require 'simplecov'
end
# @note must be before loading config/environment because railtie needs to be loaded before # @note must be before loading config/environment because railtie needs to be loaded before
# `Metasploit::Framework::Application.initialize!` is called. # `Metasploit::Framework::Application.initialize!` is called.