mirror of
https://github.com/rapid7/metasploit-payloads
synced 2024-11-20 14:39:22 +01:00
Add RSpec & raising errors when files are inaccessible
This commit is contained in:
parent
31c7726995
commit
de45e2f3b8
1
gem/.gitignore
vendored
1
gem/.gitignore
vendored
@ -7,6 +7,7 @@ InstalledFiles
|
||||
lib/bundler/man
|
||||
rdoc
|
||||
spec/reports
|
||||
spec/examples.txt
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
|
1
gem/.rspec
Normal file
1
gem/.rspec
Normal file
@ -0,0 +1 @@
|
||||
--require spec_helper
|
@ -2,3 +2,7 @@ source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in meterpreter_binaries.gemspec
|
||||
gemspec
|
||||
|
||||
group :test do
|
||||
gem 'rspec'
|
||||
end
|
||||
|
@ -1,6 +1,8 @@
|
||||
# -*- coding:binary -*-
|
||||
|
||||
require 'openssl' unless defined? OpenSSL::Digest
|
||||
require 'metasploit-payloads/version' unless defined? MetasploitPayloads::VERSION
|
||||
require 'metasploit-payloads/error' unless defined? MetasploitPayloads::Error
|
||||
|
||||
#
|
||||
# This module dispenses Metasploit payload binary files
|
||||
@ -10,6 +12,56 @@ module MetasploitPayloads
|
||||
METERPRETER_SUBFOLDER = 'meterpreter'
|
||||
USER_DATA_SUBFOLDER = 'payloads'
|
||||
|
||||
#
|
||||
# @return [Array<Hash<String, Symbol>>] An array of filenames with warnings. Provides a file name and error.
|
||||
# Empty if all needed Meterpreter files exist and have the correct hash.
|
||||
def self.manifest_errors
|
||||
manifest_errors = []
|
||||
|
||||
begin
|
||||
manifest_contents = ::File.binread(manifest_path)
|
||||
rescue => e
|
||||
return [{ path: manifest_path, error: e }]
|
||||
end
|
||||
|
||||
begin
|
||||
manifest_uuid_contents = ::File.binread(manifest_uuid_path)
|
||||
rescue => e
|
||||
manifest_errors.append({ path: manifest_uuid_path, error: e })
|
||||
end
|
||||
|
||||
# Check if the hash of the manifest file is correct.
|
||||
if manifest_uuid_contents
|
||||
manifest_digest = ::OpenSSL::Digest.new('SHA3-256', manifest_contents)
|
||||
uuid_matches = (manifest_uuid_contents.chomp == manifest_digest.to_s)
|
||||
unless uuid_matches
|
||||
e = ::MetasploitPayloads::HashMismatchError.new(manifest_path)
|
||||
manifest_errors.append({ path: manifest_path, error: e })
|
||||
end
|
||||
end
|
||||
|
||||
manifest_contents.each_line do |line|
|
||||
filename, hash_type, hash = line.chomp.split(':')
|
||||
begin
|
||||
# self.path prepends the gem data directory, which is already present in the manifest file.
|
||||
out_path = self.path(filename.sub('./data/', ''))
|
||||
# self.path can return a path to the gem data, or user's local data.
|
||||
bundled_file = out_path.start_with?(data_directory)
|
||||
if bundled_file
|
||||
file_hash_match = (::OpenSSL::Digest.new(hash_type, ::File.binread(out_path)).to_s == hash)
|
||||
unless file_hash_match
|
||||
e = ::MetasploitPayloads::HashMismatchError.new(out_path)
|
||||
manifest_errors.append({ path: e.path, error: e })
|
||||
end
|
||||
end
|
||||
rescue ::MetasploitPayloads::NotFoundError, ::MetasploitPayloads::NotReadableError => e
|
||||
manifest_errors.append({ path: e.path, error: e })
|
||||
end
|
||||
end
|
||||
|
||||
manifest_errors
|
||||
end
|
||||
|
||||
#
|
||||
# Get the path to an extension based on its name (no prefix).
|
||||
#
|
||||
@ -17,6 +69,14 @@ module MetasploitPayloads
|
||||
path(METERPRETER_SUBFOLDER, "#{EXTENSION_PREFIX}#{ext_name}.#{binary_suffix}")
|
||||
end
|
||||
|
||||
#
|
||||
# Get the path for the first readable path in the provided arguments.
|
||||
# Start with the provided `extra_paths` then fall back to the `gem_path`.
|
||||
#
|
||||
# @param [String] gem_path a path to the gem
|
||||
# @param [Array<String>] extra_paths a path to any extra paths that should be evaluated for local files before `gem_path`
|
||||
# @raise [NotReadableError] if the user doesn't have read permissions for the currently-evaluated path
|
||||
# @return [String,nil] A readable path or nil
|
||||
def self.readable_path(gem_path, *extra_paths)
|
||||
# Try the MSF path first to see if the file exists, allowing the MSF data
|
||||
# folder to override what is in the gem. This is very helpful for
|
||||
@ -24,12 +84,18 @@ module MetasploitPayloads
|
||||
# each time. We only do this is MSF is installed.
|
||||
extra_paths.each do |extra_path|
|
||||
if ::File.readable? extra_path
|
||||
warn_local_path(extra_path) if ::File.readable? gem_path
|
||||
warn_local_path(extra_path)
|
||||
return extra_path
|
||||
else
|
||||
# Raise rather than falling back;
|
||||
# If there is a local file present, let's assume that the user wants to use it (e.g. local dev. changes)
|
||||
# rather than having MSF Console falling back to the files in the gem
|
||||
raise ::MetasploitPayloads::NotReadableError, extra_path, caller if ::File.exist?(extra_path)
|
||||
end
|
||||
end
|
||||
|
||||
return gem_path if ::File.readable? gem_path
|
||||
raise ::MetasploitPayloads::NotReadableError, gem_path, caller if ::File.exist?(gem_path)
|
||||
|
||||
nil
|
||||
end
|
||||
@ -37,21 +103,36 @@ module MetasploitPayloads
|
||||
#
|
||||
# Get the path to a meterpreter binary by full name.
|
||||
#
|
||||
# @param [String] name The name of the requested binary without any file extensions
|
||||
# @param [String] binary_suffix The binary extension, without the leading '.' char (e.g. `php`, `jar`)
|
||||
# @param [Boolean] debug Request a debug version of the binary. This adds a
|
||||
# leading '.debug' to the extension if looking for a DLL file.
|
||||
def self.meterpreter_path(name, binary_suffix, debug: false)
|
||||
binary_suffix = binary_suffix&.gsub(/dll$/, 'debug.dll') if debug
|
||||
path(METERPRETER_SUBFOLDER, "#{name}.#{binary_suffix}".downcase)
|
||||
end
|
||||
|
||||
#
|
||||
# Get the full path to any file packaged in this gem by local path and name.
|
||||
# Get the full path to any file packaged in this gem or other Metasploit Framework directories by local path and name.
|
||||
#
|
||||
# @param [Array<String>] path_parts requested path parts that will be joined
|
||||
# @raise [NotFoundError] if the requested path/file does not exist
|
||||
# @raise [NotReadableError] if the requested file exists but the user doesn't have read permissions
|
||||
# @return [String,nil] A path or nil
|
||||
def self.path(*path_parts)
|
||||
gem_path = expand(data_directory, ::File.join(path_parts))
|
||||
if metasploit_installed?
|
||||
user_path = expand(Msf::Config.config_directory, ::File.join(USER_DATA_SUBFOLDER, path_parts))
|
||||
msf_path = expand(Msf::Config.data_directory, ::File.join(path_parts))
|
||||
out_path = readable_path(gem_path, user_path, msf_path)
|
||||
else
|
||||
out_path = readable_path(gem_path)
|
||||
end
|
||||
readable_path(gem_path, user_path, msf_path)
|
||||
|
||||
return out_path unless out_path.nil?
|
||||
raise ::MetasploitPayloads::NotFoundError, ::File.join(gem_path), caller unless ::File.exist?(gem_path)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
@ -61,7 +142,7 @@ module MetasploitPayloads
|
||||
file_path = path(path_parts)
|
||||
if file_path.nil?
|
||||
full_path = ::File.join(path_parts)
|
||||
fail RuntimeError, "#{full_path} not found", caller
|
||||
raise ::MetasploitPayloads::NotFoundError, full_path, caller
|
||||
end
|
||||
|
||||
::File.binread(file_path)
|
||||
@ -203,5 +284,13 @@ module MetasploitPayloads
|
||||
|
||||
things
|
||||
end
|
||||
|
||||
def manifest_path
|
||||
::File.realpath(::File.join(::File.dirname(__FILE__), '..', 'manifest'))
|
||||
end
|
||||
|
||||
def manifest_uuid_path
|
||||
::File.realpath(::File.join(::File.dirname(__FILE__), '..', 'manifest.uuid'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
36
gem/lib/metasploit-payloads/error.rb
Normal file
36
gem/lib/metasploit-payloads/error.rb
Normal file
@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module MetasploitPayloads
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
# Error raised when a Metasploit Payloads file doesn't exist.
|
||||
class NotFoundError < Error
|
||||
attr_reader :path
|
||||
|
||||
def initialize(path = '')
|
||||
@path = path
|
||||
super("Meterpreter path #{@path} not found. Ensure antivirus is not enabled, or reinstall Metasploit.")
|
||||
end
|
||||
end
|
||||
|
||||
# Error raised when the user does not have read permissions for a Metasploit Payloads file
|
||||
class NotReadableError < Error
|
||||
attr_reader :path
|
||||
|
||||
def initialize(path = '')
|
||||
@path = path
|
||||
super("Meterpreter path #{@path} is not readable. Check if you have read access and try again.")
|
||||
end
|
||||
end
|
||||
|
||||
# Error raised when a Metasploit Payloads file's hash does not match what is defined in the manifest file.
|
||||
class HashMismatchError < Error
|
||||
attr_reader :path
|
||||
|
||||
def initialize(path = '')
|
||||
@path = path
|
||||
super("Meterpreter path #{@path} does not match the hash defined in the Metasploit Payloads manifest file.")
|
||||
end
|
||||
end
|
||||
end
|
249
gem/spec/metasploit_payloads/metasploit_payloads_spec.rb
Normal file
249
gem/spec/metasploit_payloads/metasploit_payloads_spec.rb
Normal file
@ -0,0 +1,249 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'metasploit-payloads'
|
||||
|
||||
RSpec.describe ::MetasploitPayloads do
|
||||
describe '::VERSION' do
|
||||
it 'has a version number' do
|
||||
expect(::MetasploitPayloads::VERSION).not_to be nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '::Error' do
|
||||
it 'has an Error class' do
|
||||
expect(::MetasploitPayloads::Error.superclass).to be(::StandardError)
|
||||
end
|
||||
|
||||
it 'has a NotFoundError class' do
|
||||
expect(::MetasploitPayloads::NotFoundError.superclass).to be(::MetasploitPayloads::Error)
|
||||
end
|
||||
|
||||
it 'has a NotReadableError class' do
|
||||
expect(::MetasploitPayloads::NotReadableError.superclass).to be(::MetasploitPayloads::Error)
|
||||
end
|
||||
|
||||
it 'has a HashMismatchError class' do
|
||||
expect(::MetasploitPayloads::HashMismatchError.superclass).to be(::MetasploitPayloads::Error)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#readable_path' do
|
||||
let(:sample_file) { { name: 'meterpreter/meterpreter.py' } }
|
||||
|
||||
before :each do
|
||||
allow(::File).to receive(:exist?).and_call_original
|
||||
allow(::File).to receive(:readable?).and_call_original
|
||||
end
|
||||
|
||||
context 'when the path is not readable' do
|
||||
it 'raises a ::MetasploitPayloads::NotReadableError' do
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(true)
|
||||
allow(::File).to receive(:readable?).with(sample_file[:name]).and_return(false)
|
||||
|
||||
expect { subject.readable_path(sample_file[:name]) }.to raise_error(::MetasploitPayloads::NotReadableError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the path does not exist' do
|
||||
it 'returns nil' do
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(false)
|
||||
allow(::File).to receive(:readable?).with(sample_file[:name]).and_return(false)
|
||||
|
||||
expect(subject.readable_path(sample_file[:name])).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the path exists and is readable' do
|
||||
it 'returns the correct path' do
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(true)
|
||||
allow(::File).to receive(:readable?).with(sample_file[:name]).and_return(true)
|
||||
|
||||
expect(subject.readable_path(sample_file[:name])).to eq(sample_file[:name])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#path' do
|
||||
let(:sample_file) { { name: 'meterpreter/meterpreter.py' } }
|
||||
|
||||
before :each do
|
||||
allow(::File).to receive(:exist?).and_call_original
|
||||
allow(::File).to receive(:readable?).and_call_original
|
||||
allow(::MetasploitPayloads).to receive(:expand).and_call_original
|
||||
|
||||
allow(::MetasploitPayloads).to receive(:expand)
|
||||
.with(::MetasploitPayloads.data_directory, sample_file[:name])
|
||||
.and_return(sample_file[:name])
|
||||
end
|
||||
|
||||
[
|
||||
{ context: 'is not readable', exist: true, readable: false, expected: ::MetasploitPayloads::NotReadableError },
|
||||
{ context: 'does not exist', exist: false, readable: false, expected: ::MetasploitPayloads::NotFoundError }
|
||||
].each do |test|
|
||||
context "when the path #{test[:context]}" do
|
||||
it "raises #{test[:expected]}" do
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(test[:exist])
|
||||
allow(::File).to receive(:readable?).with(sample_file[:name]).and_return(test[:readable])
|
||||
|
||||
expect { subject.path(sample_file[:name]) }.to raise_error(test[:expected])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the path exists and is readable' do
|
||||
it 'returns the correct path' do
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(true)
|
||||
allow(::File).to receive(:readable?).with(sample_file[:name]).and_return(true)
|
||||
|
||||
expect(subject.path(sample_file[:name])).to eq(sample_file[:name])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#manifest_errors' do
|
||||
let(:hash_type) { 'SHA3-256' }
|
||||
let(:hash) { { type: hash_type, value: '92e931e6b47caad6df4249cc263fdbe5d2975c4163f5b06963208163b7af97b5' } }
|
||||
let(:sample_file) { { name: 'meterpreter/ext_server_stdapi.php', contents: 'sample_data', hash: hash } }
|
||||
let(:manifest_values) { ["./data/#{sample_file[:name]}", sample_file[:hash][:type], sample_file[:hash][:value]] }
|
||||
let(:manifest) { manifest_values.join(':') }
|
||||
let(:manifest_uuid) { ::OpenSSL::Digest.new(hash_type, manifest).to_s }
|
||||
let(:manifest_path) { 'manifest' }
|
||||
let(:manifest_uuid_path) { 'manifest.uuid' }
|
||||
|
||||
before :each do
|
||||
allow(::MetasploitPayloads).to receive(:manifest_path).and_call_original
|
||||
allow(::MetasploitPayloads).to receive(:manifest_path).and_return(manifest_path)
|
||||
|
||||
allow(::MetasploitPayloads).to receive(:manifest_uuid_path).and_call_original
|
||||
allow(::MetasploitPayloads).to receive(:manifest_uuid_path).and_return(manifest_uuid_path)
|
||||
|
||||
allow(::File).to receive(:binread).and_call_original
|
||||
allow(::File).to receive(:binread).with(sample_file[:name]).and_return(sample_file[:contents])
|
||||
allow(::File).to receive(:binread).with(::MetasploitPayloads.send(:manifest_path)).and_return(manifest)
|
||||
allow(::File).to receive(:binread).with(::MetasploitPayloads.send(:manifest_uuid_path)).and_return(manifest_uuid)
|
||||
|
||||
allow(::OpenSSL::Digest).to receive(:new).and_call_original
|
||||
allow(::OpenSSL::Digest).to receive(:new).with(hash_type,
|
||||
sample_file[:contents]).and_return(sample_file[:hash][:value])
|
||||
end
|
||||
|
||||
context 'when manifest hash does not match' do
|
||||
it 'result includes the manifest file' do
|
||||
allow(::File).to receive(:binread).with(::MetasploitPayloads.send(:manifest_uuid_path))
|
||||
.and_return('mismatched_manifest_hash')
|
||||
path = ::MetasploitPayloads.send(:manifest_path)
|
||||
e = ::MetasploitPayloads::HashMismatchError.new(path)
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: path, error: e })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when manifest hash does match' do
|
||||
it 'result does not include manifest' do
|
||||
path = ::MetasploitPayloads.send(:manifest_uuid_path)
|
||||
e = ::MetasploitPayloads::HashMismatchError.new(path)
|
||||
|
||||
expect(subject.manifest_errors).not_to include({ path: path, error: e })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no file warnings' do
|
||||
it 'returns an empty array' do
|
||||
allow(::MetasploitPayloads).to receive(:path).with(sample_file[:name]).and_return(sample_file[:name])
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(true)
|
||||
full_file_path = ::MetasploitPayloads.expand(::MetasploitPayloads.data_directory, sample_file[:name])
|
||||
allow(::File).to receive(:readable?).with(full_file_path).and_return(true)
|
||||
allow(::File).to receive(:binread).with(full_file_path).and_return(sample_file[:contents])
|
||||
|
||||
expect(subject.manifest_errors).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
[
|
||||
{ context: 'does not exist', error_class: ::MetasploitPayloads::NotFoundError },
|
||||
{ context: 'is not readable', error_class: ::MetasploitPayloads::NotReadableError }
|
||||
].each do |test|
|
||||
context "when a file #{test[:context]}" do
|
||||
it 'includes the correct error' do
|
||||
error = test[:error_class].new(sample_file[:name])
|
||||
allow(::MetasploitPayloads).to receive(:path).with(sample_file[:name]).and_raise(error)
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: sample_file[:name], error: error })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a bundled file hash does not match' do
|
||||
it 'includes the correct error' do
|
||||
allow(::File).to receive(:exist?).with(sample_file[:name]).and_return(true)
|
||||
full_file_path = ::MetasploitPayloads.expand(::MetasploitPayloads.data_directory, sample_file[:name])
|
||||
allow(::File).to receive(:readable?).with(full_file_path).and_return(true)
|
||||
allow(::File).to receive(:binread).with(full_file_path).and_return('mismatched_file_contents')
|
||||
e = ::MetasploitPayloads::HashMismatchError.new(full_file_path)
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: full_file_path, error: e })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the manifest file' do
|
||||
context 'does not exist' do
|
||||
it 'only includes the manifest error' do
|
||||
# path = ::MetasploitPayloads.send(:manifest_path)
|
||||
e = ::Errno::ENOENT.new(manifest_path)
|
||||
allow(::File).to receive(:binread).with(manifest_path).and_raise(e)
|
||||
|
||||
expect(subject.manifest_errors).to eq([{ path: manifest_path, error: e }])
|
||||
end
|
||||
end
|
||||
|
||||
context 'cannot be read' do
|
||||
it 'only includes the manifest error' do
|
||||
e = ::Errno::EACCES.new(manifest_path)
|
||||
allow(::File).to receive(:binread).with(manifest_path).and_raise(e)
|
||||
|
||||
expect(subject.manifest_errors).to eq([{ path: manifest_path, error: e }])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the manifest.uuid file' do
|
||||
context 'does not exist' do
|
||||
it 'includes the correct error' do
|
||||
e = ::Errno::ENOENT.new(manifest_uuid_path)
|
||||
allow(::File).to receive(:binread).with(manifest_uuid_path).and_raise(e)
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: manifest_uuid_path, error: e })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when manifest is readable and manifest.uuid is not readable' do
|
||||
before :each do
|
||||
allow(::File).to receive(:binread).with(manifest_uuid_path).and_raise(::Errno::EACCES.new(manifest_uuid_path))
|
||||
end
|
||||
|
||||
it 'correctly evaluates a file hash mismatch' do
|
||||
bundled_file_path = ::MetasploitPayloads.expand(::MetasploitPayloads.data_directory, sample_file[:name])
|
||||
error = ::MetasploitPayloads::HashMismatchError.new(bundled_file_path)
|
||||
allow(::MetasploitPayloads).to receive(:path).with(sample_file[:name]).and_return(bundled_file_path)
|
||||
allow(::File).to receive(:binread).with(bundled_file_path).and_return('sample_mismatched_contents')
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: bundled_file_path, error: error })
|
||||
end
|
||||
|
||||
it 'correctly evaluates a missing file' do
|
||||
error = ::MetasploitPayloads::NotFoundError.new(sample_file[:name])
|
||||
allow(::MetasploitPayloads).to receive(:path).with(sample_file[:name]).and_raise(error)
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: sample_file[:name], error: error })
|
||||
end
|
||||
|
||||
it 'correctly evaluates an unreadable file' do
|
||||
error = ::MetasploitPayloads::NotReadableError.new(sample_file[:name])
|
||||
allow(::MetasploitPayloads).to receive(:path).with(sample_file[:name]).and_raise(error)
|
||||
|
||||
expect(subject.manifest_errors).to include({ path: sample_file[:name], error: error })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
103
gem/spec/spec_helper.rb
Normal file
103
gem/spec/spec_helper.rb
Normal file
@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'metasploit_payloads/metasploit_payloads_spec'
|
||||
|
||||
# This file was generated by the `rspec --init` command. Conventionally, all
|
||||
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
||||
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
||||
# this file to always be loaded, without a need to explicitly require it in any
|
||||
# files.
|
||||
#
|
||||
# Given that it is always loaded, you are encouraged to keep this file as
|
||||
# light-weight as possible. Requiring heavyweight dependencies from this file
|
||||
# will add to the boot time of your test suite on EVERY test run, even for an
|
||||
# individual file that may not need all of that loaded. Instead, consider making
|
||||
# a separate helper file that requires the additional dependencies and performs
|
||||
# the additional setup, and require it from the spec files that actually need
|
||||
# it.
|
||||
#
|
||||
# The `.rspec` file also contains a few flags that are not defaults but that
|
||||
# users commonly want.
|
||||
#
|
||||
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
||||
RSpec.configure do |config|
|
||||
# rspec-expectations config goes here. You can use an alternate
|
||||
# assertion/expectation library such as wrong or the stdlib/minitest
|
||||
# assertions if you prefer.
|
||||
config.expect_with :rspec do |expectations|
|
||||
# This option will default to `true` in RSpec 4. It makes the `description`
|
||||
# and `failure_message` of custom matchers include text for helper methods
|
||||
# defined using `chain`, e.g.:
|
||||
# be_bigger_than(2).and_smaller_than(4).description
|
||||
# # => "be bigger than 2 and smaller than 4"
|
||||
# ...rather than:
|
||||
# # => "be bigger than 2"
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
||||
# rspec-mocks config goes here. You can use an alternate test double
|
||||
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
||||
config.mock_with :rspec do |mocks|
|
||||
# Prevents you from mocking or stubbing a method that does not exist on
|
||||
# a real object. This is generally recommended, and will default to
|
||||
# `true` in RSpec 4.
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
|
||||
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
||||
# have no way to turn it off -- the option exists only for backwards
|
||||
# compatibility in RSpec 3). It causes shared context metadata to be
|
||||
# inherited by the metadata hash of host groups and examples, rather than
|
||||
# triggering implicit auto-inclusion in groups with matching metadata.
|
||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||
|
||||
# The settings below are suggested to provide a good initial experience
|
||||
# with RSpec, but feel free to customize to your heart's content.
|
||||
# This allows you to limit a spec run to individual examples or groups
|
||||
# you care about by tagging them with `:focus` metadata. When nothing
|
||||
# is tagged with `:focus`, all examples get run. RSpec also provides
|
||||
# aliases for `it`, `describe`, and `context` that include `:focus`
|
||||
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
||||
config.filter_run_when_matching :focus
|
||||
|
||||
# Allows RSpec to persist some state between runs in order to support
|
||||
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
||||
# you configure your source control system to ignore this file.
|
||||
config.example_status_persistence_file_path = 'spec/examples.txt'
|
||||
|
||||
# Limits the available syntax to the non-monkey patched syntax that is
|
||||
# recommended. For more details, see:
|
||||
# https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/
|
||||
config.disable_monkey_patching!
|
||||
|
||||
# This setting enables warnings. It's recommended, but in some cases may
|
||||
# be too noisy due to issues in dependencies.
|
||||
config.warnings = true
|
||||
|
||||
# Many RSpec users commonly either run the entire suite or an individual
|
||||
# file, and it's useful to allow more verbose output when running an
|
||||
# individual spec file.
|
||||
if config.files_to_run.one?
|
||||
# Use the documentation formatter for detailed output,
|
||||
# unless a formatter has already been configured
|
||||
# (e.g. via a command-line flag).
|
||||
config.default_formatter = 'doc'
|
||||
end
|
||||
|
||||
# Print the 10 slowest examples and example groups at the
|
||||
# end of the spec run, to help surface which specs are running
|
||||
# particularly slow.
|
||||
config.profile_examples = 10
|
||||
|
||||
# Run specs in random order to surface order dependencies. If you find an
|
||||
# order dependency and want to debug it, you can fix the order by providing
|
||||
# the seed, which is printed after each run.
|
||||
# --seed 1234
|
||||
config.order = :random
|
||||
|
||||
# Seed global randomization in this process using the `--seed` CLI option.
|
||||
# Setting this allows you to use `--seed` to deterministically reproduce
|
||||
# test failures related to randomization by passing the same `--seed` value
|
||||
# as the one that triggered the failure.
|
||||
Kernel.srand config.seed
|
||||
end
|
Loading…
Reference in New Issue
Block a user