26 changed files with 789 additions and 203 deletions
@ -0,0 +1,2 @@ |
|||
--require spec_helper |
|||
--require rails_helper |
@ -0,0 +1,16 @@ |
|||
module Utils |
|||
def self.utf8!(text) |
|||
return nil unless text |
|||
return text if text.encoding == Encoding::UTF_8 |
|||
text.force_encoding 'utf-8' |
|||
end |
|||
|
|||
def self.diff(a, b, context: 3, limit: 30) |
|||
a = self.utf8! a |
|||
b = self.utf8! b |
|||
diff = Diffy::Diff.new a, b, context: context |
|||
diff = diff.to_s :color |
|||
return '...(too much diff)...'.colorize :light_red if diff.lines.size > limit |
|||
diff |
|||
end |
|||
end |
@ -0,0 +1,69 @@ |
|||
class Check < ApplicationRecord |
|||
belongs_to :site |
|||
belongs_to :target |
|||
|
|||
def reference!(content) |
|||
target = self.target |
|||
reference = target.extract content |
|||
self.update! reference: reference, content: nil, checked_at: Time.now, changed_at: nil, last_error: nil |
|||
end |
|||
|
|||
def diff!(content, debug: false) |
|||
return :previously_changed if self.changed_at |
|||
|
|||
self.checked_at = Time.now |
|||
state = :unchanged |
|||
|
|||
begin |
|||
target = self.target |
|||
reference = Utils.utf8! self.reference |
|||
content = target.extract content |
|||
changed = reference != content |
|||
if changed |
|||
puts Utils.diff reference, content if debug |
|||
state = :changed |
|||
self.content = content |
|||
self.changed_at = self.checked_at |
|||
end |
|||
self.last_error = nil |
|||
rescue => e |
|||
raise |
|||
$stderr.puts e |
|||
state = :error |
|||
self.last_error = e |
|||
end |
|||
|
|||
self.save! |
|||
state |
|||
end |
|||
|
|||
def recalculate!(debug: false) |
|||
state = :unchanged |
|||
|
|||
target = self.target |
|||
reference = self.site.reference |
|||
content = self.site.content || reference |
|||
|
|||
reference = target.extract reference |
|||
content = target.extract content |
|||
|
|||
changed_at = self.changed_at |
|||
|
|||
if reference == content |
|||
content = nil |
|||
changed_at = nil |
|||
else |
|||
puts Utils.diff reference, content if debug |
|||
state = :changed |
|||
changed_at ||= self.checked_at |
|||
end |
|||
|
|||
self.update! reference: reference, content: content, changed_at: changed_at |
|||
|
|||
state |
|||
end |
|||
|
|||
def clear! |
|||
self.update! reference: nil, content: nil, checked_at: nil, changed_at: nil, last_error: nil |
|||
end |
|||
end |
@ -1,2 +1,59 @@ |
|||
class Target < ApplicationRecord |
|||
has_many :templates |
|||
has_many :groups |
|||
has_many :sites |
|||
has_many :checks |
|||
|
|||
def to_s |
|||
s = [] |
|||
s << self.name if self.name |
|||
s << "from: #{self.from}" if self.from |
|||
s << "to: #{self.to}" if self.to |
|||
s << "css: #{self.css}" if self.css |
|||
s.join ' ' |
|||
end |
|||
|
|||
def extract_boundary(content) |
|||
return nil unless content |
|||
if self.from |
|||
i = content.index self.from |
|||
unless i |
|||
# $stderr.puts "Unable to find `from` #{self.from}" |
|||
return nil |
|||
raise "Unable to find `from` #{self.from}" |
|||
end |
|||
content = content[i..-1] |
|||
end |
|||
|
|||
if self.to |
|||
i = content.index self.to |
|||
unless i |
|||
# $stderr.puts "Unable to find `to` #{self.to}" |
|||
return nil |
|||
raise "Unable to find `to` #{self.to}" |
|||
end |
|||
content = content[0..i+self.to.size] |
|||
end |
|||
content |
|||
end |
|||
|
|||
def extract_css(content) |
|||
return nil unless content |
|||
return content unless self.css |
|||
content = Nokogiri::HTML.parse content |
|||
node = content.at self.css |
|||
unless node |
|||
# $stderr.puts "Unable to find `css` #{self.css}" |
|||
return nil |
|||
raise "Unable to find `css` #{self.css}" |
|||
end |
|||
node.to_s |
|||
end |
|||
|
|||
def extract(content) |
|||
return nil unless content |
|||
content = self.extract_boundary content |
|||
content = self.extract_css content |
|||
content |
|||
end |
|||
end |
|||
|
@ -1,88 +0,0 @@ |
|||
class Targets < ActiveRecord::Type::Value |
|||
class Target |
|||
def initialize(target) |
|||
@from = target['from'] |
|||
@to = target['to'] |
|||
@css = target['css'] |
|||
end |
|||
|
|||
def extract_boundary(content) |
|||
if @from |
|||
i = content.index @from |
|||
raise "Unable to find `from` #{@from}" unless i |
|||
content = content[i..-1] |
|||
end |
|||
|
|||
if @to |
|||
i = content.index @to |
|||
raise "Unable to find `to` #{@to}" unless i |
|||
content = content[0..i] |
|||
end |
|||
content |
|||
end |
|||
|
|||
def extract_css(content) |
|||
return content unless @css |
|||
content = Nokogiri::HTML.parse content |
|||
node = content.at @css |
|||
raise "Unable to find `css` #{@css}" unless node |
|||
node.to_s |
|||
end |
|||
|
|||
def extract(content) |
|||
content = self.extract_boundary content |
|||
content = self.extract_css content |
|||
content |
|||
end |
|||
|
|||
def to_h |
|||
json = {} |
|||
json['from'] = @from if @from |
|||
json['to'] = @to if @to |
|||
json['css'] = @css if @css |
|||
json |
|||
end |
|||
|
|||
def empty? |
|||
!(@from || @to || @css) |
|||
end |
|||
end |
|||
|
|||
def self.detect(object) |
|||
targets = object['targets'] |
|||
if targets |
|||
targets = targets.collect { |t| create t }.flatten |
|||
return nil if targets.empty? |
|||
targets |
|||
end |
|||
|
|||
target = create object |
|||
return nil unless target |
|||
|
|||
[target] |
|||
end |
|||
|
|||
def type |
|||
:string |
|||
end |
|||
|
|||
def deserialize(value) |
|||
return nil unless value |
|||
value = YAML.load value |
|||
value.collect { |t| Target.new t } |
|||
end |
|||
|
|||
def serialize(value) |
|||
return nil unless value |
|||
value = value.collect &:to_h |
|||
YAML.dump value |
|||
end |
|||
|
|||
private |
|||
|
|||
def self.create(target) |
|||
target = Target.new target |
|||
return nil if target.empty? |
|||
target |
|||
end |
|||
end |
@ -1,18 +0,0 @@ |
|||
#!./bin/rails runner |
|||
Parallel.each Site.all, in_threads: 5 do |site| |
|||
ActiveRecord::Base.transaction do |
|||
print "Checking #{site.url.colorize :yellow}..." |
|||
begin |
|||
result = site.check |
|||
color = case result |
|||
when :new |
|||
:blue |
|||
when :changes |
|||
:green |
|||
end |
|||
puts " #{result.to_s.colorize color}" |
|||
rescue => e |
|||
puts " #{e.to_s.colorize :red}" |
|||
end |
|||
end |
|||
end |
@ -0,0 +1,113 @@ |
|||
#!./bin/rails runner |
|||
require 'ostruct' |
|||
require 'optparse' |
|||
|
|||
# Force resolution to avoid cycle in autoloading |
|||
Check |
|||
Target |
|||
Site |
|||
Group |
|||
Template |
|||
|
|||
def fp(content) |
|||
return nil unless content |
|||
Digest::SHA1.hexdigest content |
|||
end |
|||
|
|||
def display(item) |
|||
reference = item.reference |
|||
content = item.content |
|||
ap reference: fp(reference), |
|||
content: fp(content), |
|||
checked_at: item.checked_at, |
|||
changed_at: item.changed_at, |
|||
last_error: item.last_error |
|||
|
|||
if reference && content && reference != content |
|||
puts Utils.diff reference, content |
|||
end |
|||
end |
|||
|
|||
class App < Thor |
|||
desc 'check', 'Check given sites for changes' |
|||
method_option :reset, type: :boolean, default: false, aliases: '-r', desc: 'Reset sites before check' |
|||
method_option :debug, type: :boolean, default: false, aliases: '-d', desc: 'Activate debug' |
|||
|
|||
COLORS = { |
|||
reference: :blue, |
|||
unchanged: :green, |
|||
previously_changed: :light_red, |
|||
changed: :red, |
|||
error: { background: :red } |
|||
}.freeze |
|||
|
|||
def check(urls = nil) |
|||
reset = options[:reset] |
|||
debug = options[:debug] |
|||
|
|||
self.process urls do |site| |
|||
site.reset! if reset |
|||
result = site.check debug: debug |
|||
color = COLORS[result] |
|||
result.to_s.colorize color |
|||
end |
|||
end |
|||
|
|||
desc 'read', 'Mark given sites as read' |
|||
|
|||
def read(urls = nil) |
|||
self.process urls, &:read! |
|||
end |
|||
|
|||
desc 'diff', 'Display diff of the given sites' |
|||
|
|||
def diff(urls = nil) |
|||
sites = self.sites urls |
|||
sites.each do |site| |
|||
next unless site.changed_at |
|||
puts "#{site.url.colorize :yellow}" |
|||
checks = site.checks |
|||
display site if checks.empty? |
|||
checks.each do |check| |
|||
next unless check.changed_at |
|||
puts " #{check.target}" |
|||
display check |
|||
end |
|||
end |
|||
end |
|||
|
|||
desc 'recalculate', 'Recalculate state of given sites' |
|||
method_option :debug, type: :boolean, default: false, aliases: '-d', desc: 'Activate debug' |
|||
|
|||
def recalculate(urls = nil) |
|||
debug = options[:debug] |
|||
self.process urls do |site| |
|||
result = site.recalculate! debug: debug |
|||
color = COLORS[result] |
|||
result.to_s.colorize color |
|||
end |
|||
end |
|||
|
|||
protected |
|||
|
|||
def sites(url) |
|||
return Site.where url: url if url |
|||
Site.all |
|||
end |
|||
|
|||
def process(urls) |
|||
sites = self.sites urls |
|||
Parallel.each sites, in_threads: 16 do |site| |
|||
ActiveRecord::Base.transaction do |
|||
url = site.url.colorize :yellow |
|||
begin |
|||
result = yield site |
|||
puts "#{url} #{result}" |
|||
rescue => e |
|||
puts "#{url} #{e.to_s.colorize :red}" |
|||
end |
|||
end |
|||
end |
|||
end |
|||
end |
|||
App.start |
@ -0,0 +1,67 @@ |
|||
#!./bin/rails runner |
|||
# id = ARGV.first |
|||
# check = Check.find id |
|||
# reference = File.join Dir.tmpdir, "#{id}-reference" |
|||
# File.write reference, check.reference |
|||
# content = File.join Dir.tmpdir, "#{id}-content" |
|||
# File.write content, check.content |
|||
# system 'kompare', reference, content |
|||
|
|||
# puts 'Recalculating...' |
|||
# Site.all.each do |site| |
|||
# puts site.url.colorize :yellow |
|||
# site.checks.each do |check| |
|||
# puts ' ' + check.target.to_s |
|||
# check.recalculate! |
|||
# end |
|||
# end |
|||
|
|||
def fp(content) |
|||
return nil unless content |
|||
Digest::SHA1.hexdigest content |
|||
end |
|||
|
|||
def display(item) |
|||
reference = item.reference&.force_encoding 'utf-8' |
|||
content = item.content&.force_encoding 'utf-8' |
|||
ap reference: fp(reference), |
|||
content: fp(content), |
|||
checked_at: item.checked_at, |
|||
changed_at: item.changed_at, |
|||
last_error: item.last_error |
|||
|
|||
if reference && content && reference != content |
|||
diff = Diffy::Diff.new reference, content, context: 3 |
|||
diff = diff.to_s :color |
|||
if diff.lines.size > 30 |
|||
puts '...(too much diff)...'.colorize :light_red |
|||
else |
|||
puts diff |
|||
end |
|||
end |
|||
end |
|||
|
|||
url = ARGV.first |
|||
sites = if url |
|||
if url == 'all' |
|||
Site.all |
|||
else |
|||
Site.where url: url |
|||
end |
|||
else |
|||
Site.where.not changed_at: nil |
|||
end |
|||
|
|||
sites.each do |site| |
|||
site.recalculate! |
|||
next unless site.changed_at |
|||
puts site.url.colorize :yellow |
|||
checks = site.checks |
|||
display site if checks.empty? |
|||
checks.each do |check| |
|||
# check.recalculate! |
|||
next unless check.changed_at |
|||
puts " #{check.target}" |
|||
display check |
|||
end |
|||
end |
@ -1 +0,0 @@ |
|||
ActiveRecord::Type.register :targets, Targets |
@ -1,11 +0,0 @@ |
|||
class CreateTargets < ActiveRecord::Migration[5.1] |
|||
def change |
|||
create_table :targets do |t| |
|||
t.belongs_to :template, index: true, foreign_key: true |
|||
t.belongs_to :group, index: true, foreign_key: true |
|||
t.belongs_to :site, index: true, foreign_key: true |
|||
|
|||
t.timestamps |
|||
end |
|||
end |
|||
end |
@ -1,13 +1,9 @@ |
|||
class CreateGroups < ActiveRecord::Migration[5.1] |
|||
def change |
|||
create_table :groups do |t| |
|||
t.string :name, null: false, unique: true |
|||
t.string :targets |
|||
t.string :name, null: false, index: { unique: true } |
|||
|
|||
t.belongs_to :template, index: true, foreign_key: true |
|||
|
|||
t.index :name, unique: true |
|||
t.index :template |
|||
end |
|||
end |
|||
end |
@ -0,0 +1,14 @@ |
|||
class CreateTargets < ActiveRecord::Migration[5.1] |
|||
def change |
|||
create_table :targets do |t| |
|||
t.string :name |
|||
t.string :css |
|||
t.string :from |
|||
t.string :to |
|||
|
|||
t.belongs_to :template, index: true, foreign_key: true |
|||
t.belongs_to :group, index: true, foreign_key: true |
|||
t.belongs_to :site, index: true, foreign_key: true |
|||
end |
|||
end |
|||
end |
@ -0,0 +1,15 @@ |
|||
class CreateChecks < ActiveRecord::Migration[5.1] |
|||
def change |
|||
create_table :checks do |t| |
|||
t.binary :reference |
|||
t.binary :content |
|||
|
|||
t.belongs_to :target, index: true, foreign_key: true |
|||
t.belongs_to :site, index: true, foreign_key: true |
|||
|
|||
t.string :last_error |
|||
t.datetime :checked_at |
|||
t.datetime :changed_at |
|||
end |
|||
end |
|||
end |
@ -0,0 +1,82 @@ |
|||
|
|||
RSpec.describe Site, type: :model do |
|||
REFERENCE = '<html><body>foo <div id="content">bar</div></body></html>' |
|||
CHANGE_OUTSIDE_TARGET = '<html><body>baz <div id="content">bar</div></body></html>' |
|||
CHANGE_INSIDE_TARGET = '<html><body>foo <div id="content">baz</div></body></html>' |
|||
|
|||
let :site do |
|||
Site.create! url: 'http://localhost/' |
|||
end |
|||
|
|||
let :check do |
|||
site.checks.first |
|||
end |
|||
|
|||
def add_check(**args) |
|||
target = site.targets.create! args |
|||
site.checks.create! target: target |
|||
end |
|||
|
|||
def stub_page(content) |
|||
allow(Site).to receive(:grab) { OpenStruct.new body: content } |
|||
end |
|||
|
|||
def check!(content) |
|||
site.reference! REFERENCE |
|||
stub_page content |
|||
site.check |
|||
end |
|||
|
|||
it 'must not change if no change with no check' do |
|||
status = check! REFERENCE |
|||
expect(status).to be :unchanged |
|||
|
|||
expect(site.changed_at).to be_nil |
|||
expect(site.content).to be_nil |
|||
end |
|||
|
|||
it 'must not change if no change with checks' do |
|||
check = add_check css: '#content' |
|||
|
|||
status = check! REFERENCE |
|||
expect(status).to be :unchanged |
|||
|
|||
expect(site.changed_at).to be_nil |
|||
expect(site.content).to be_nil |
|||
|
|||
expect(check.changed_at).to be_nil |
|||
expect(check.content).to be_nil |
|||
end |
|||
|
|||
it 'must change if change with no check' do |
|||
status = check! CHANGE_OUTSIDE_TARGET |
|||
expect(status).to be :changed |
|||
|
|||
expect(site.changed_at).not_to be_nil |
|||
expect(site.content).not_to be_nil |
|||
end |
|||
|
|||
it 'must not change if change but no check changed' do |
|||
check = add_check css: '#content' |
|||
status = check! CHANGE_OUTSIDE_TARGET |
|||
expect(status).to be :unchanged |
|||
|
|||
expect(site.changed_at).to be_nil |
|||
expect(site.content).to be_nil |
|||
|
|||
expect(check.changed_at).to be_nil |
|||
expect(check.content).to be_nil |
|||
end |
|||
|
|||
it 'must change if check changed' do |
|||
check = add_check css: '#content' |
|||
status = check! CHANGE_INSIDE_TARGET |
|||
expect(status).to be :changed |
|||
|
|||
expect(site.changed_at).not_to be_nil |
|||
expect(site.content).not_to be_nil |
|||
|
|||
expect(check.changed_at).not_to be_nil |
|||
expect(check.content).not_to be_nil |
|||
end |
|||
end |
@ -0,0 +1,57 @@ |
|||
# This file is copied to spec/ when you run 'rails generate rspec:install' |
|||
require 'spec_helper' |
|||
ENV['RAILS_ENV'] ||= 'test' |
|||
require File.expand_path('../../config/environment', __FILE__) |
|||
# Prevent database truncation if the environment is production |
|||
abort("The Rails environment is running in production mode!") if Rails.env.production? |
|||
require 'rspec/rails' |
|||
# Add additional requires below this line. Rails is not loaded until this point! |
|||
|
|||
# Requires supporting ruby files with custom matchers and macros, etc, in |
|||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are |
|||
# run as spec files by default. This means that files in spec/support that end |
|||
# in _spec.rb will both be required and run as specs, causing the specs to be |
|||
# run twice. It is recommended that you do not name files matching this glob to |
|||
# end with _spec.rb. You can configure this pattern with the --pattern |
|||
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. |
|||
# |
|||
# The following line is provided for convenience purposes. It has the downside |
|||
# of increasing the boot-up time by auto-requiring all files in the support |
|||
# directory. Alternatively, in the individual `*_spec.rb` files, manually |
|||
# require only the support files necessary. |
|||
# |
|||
# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } |
|||
|
|||
# Checks for pending migrations and applies them before tests are run. |
|||
# If you are not using ActiveRecord, you can remove this line. |
|||
ActiveRecord::Migration.maintain_test_schema! |
|||
|
|||
RSpec.configure do |config| |
|||
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures |
|||
config.fixture_path = "#{::Rails.root}/spec/fixtures" |
|||
|
|||
# If you're not using ActiveRecord, or you'd prefer not to run each of your |
|||
# examples within a transaction, remove the following line or assign false |
|||
# instead of true. |
|||
config.use_transactional_fixtures = true |
|||
|
|||
# RSpec Rails can automatically mix in different behaviours to your tests |
|||
# based on their file location, for example enabling you to call `get` and |
|||
# `post` in specs under `spec/controllers`. |
|||
# |
|||
# You can disable this behaviour by removing the line below, and instead |
|||
# explicitly tag your specs with their type, e.g.: |
|||
# |
|||
# RSpec.describe UsersController, :type => :controller do |
|||
# # ... |
|||
# end |
|||
# |
|||
# The different available types are documented in the features, such as in |
|||
# https://relishapp.com/rspec/rspec-rails/docs |
|||
config.infer_spec_type_from_file_location! |
|||
|
|||
# Filter lines from Rails gems in backtraces. |
|||
config.filter_rails_from_backtrace! |
|||
# arbitrary gems may also be filtered via: |
|||
# config.filter_gems_from_backtrace("gem name") |
|||
end |
@ -0,0 +1,96 @@ |
|||
# This file was generated by the `rails generate rspec:install` 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. |
|||
# |
|||
# 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. |
|||
=begin |
|||
# 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: |
|||
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ |
|||
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ |
|||
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode |
|||
config.disable_monkey_patching! |
|||
|
|||
# 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 |
|||
end |
Loading…
Reference in new issue