Class: Spectre::Engine

Inherits:
Object show all
Defined in:
lib/spectre.rb

Constant Summary collapse

@@current =
nil
@@modules =
[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Engine

Returns a new instance of Engine.



1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
# File 'lib/spectre.rb', line 1502

def initialize config
  @environments = {}
  @collections = {}
  @contexts = []
  @mixins = {}
  @resources = {}
  @delegates = {}

  @config = Marshal.load(Marshal.dump(CONFIG))

  # Load global config file
  global_config_file = config['global_config_file'] || File.expand_path('~/.config/spectre.yml')

  if File.exist? global_config_file
    global_config = load_yaml(global_config_file)
    @config.deep_merge!(global_config)
  end

  # Set working directory so all paths in config
  # are relative to this directory
  Dir.chdir(config['work_dir'] || @config['work_dir'] || '.')

  # Load main spectre config
  main_config_file = config['config_file'] || @config['config_file']

  unless main_config_file.nil? or !File.exist? main_config_file
    main_config = load_yaml(main_config_file)
    @config.deep_merge!(main_config)
    Dir.chdir(File.dirname(main_config_file))
  end

  # Load environments
  @config['env_patterns'].each do |pattern|
    Dir.glob(pattern).each do |file_path|
      loaded_env = load_yaml(file_path)
      env_name = loaded_env['name'] || DEFAULT_ENV_NAME
      @environments[env_name] = loaded_env
    end
  end

  # Load and merge partial environment files
  @config['env_partial_patterns'].each do |pattern|
    Dir.glob(pattern).each do |file_path|
      loaded_env = load_yaml(file_path)
      env_name = loaded_env['name'] || DEFAULT_ENV_NAME
      @environments[env_name].deep_merge!(loaded_env) if @environments.key?(env_name)
    end
  end

  # Select environment and merge it
  @config.deep_merge!(@environments[config.delete('selected_env') || DEFAULT_ENV_NAME])

  # Load collections
  @config['collections_patterns'].each do |pattern|
    Dir.glob(pattern).each do |file_path|
      @collections.merge! load_yaml(file_path)
    end
  end

  # Use collection if given
  if config.key? 'collection'
    collection = @collections[config['collection']]

    raise "collection #{config['collection']} not found" unless collection

    @config.deep_merge!(collection)
  end

  # Merge property overrides
  # Merging would override arrays. We don't want this in certain cases,
  # so merge them manually
  @config['reporters'].concat(config.delete('reporters')) if config.key? 'reporters'
  @config['modules'].concat(config.delete('modules')) if config.key? 'modules'
  @config.deep_merge!(config)

  # Replace log filename placeholders
  if @config['log_file'].respond_to? :gsub!
    @config['log_file'].gsub!('<date>', DateTime.now.strftime('%Y-%m-%d_%H%M%S%3N'))
  end

  # Set env before loading specs in order to make it available in spec definitions
  @env = @config.to_recursive_struct

  # Load specs
  # Note that spec files are only loaded once, because of the relative require,
  # even if the setup function is called multiple times
  load_files(@config['spec_patterns'])

  # Load mixins
  # Mixins are also only loaded once
  load_files(@config['mixin_patterns'])

  # Load resources
  @config['resource_paths'].each do |resource_path|
    resource_files = Dir.glob File.join(resource_path, '**/*')

    resource_files.each do |file|
      relative_file = file
        .delete_prefix(resource_path)
        .delete_prefix('/')

      @resources[relative_file] = File.expand_path(file)
    end
  end

  @formatter = Object
    .const_get(@config['formatter'])
    .new(@config)

  # Load modules
  return unless @config['modules'].is_a? Array

  @config['modules'].each do |module_name|
    module_path = File.join(Dir.pwd, module_name)

    if File.exist? module_path
      require_relative module_path
    else
      require module_name
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method) ⇒ Object

:nodoc:



1631
1632
1633
# File 'lib/spectre.rb', line 1631

def method_missing(method, *, **, &)
  @delegates[method]&.send(method, *, **, &)
end

Instance Attribute Details

#collectionsObject (readonly)

Returns the value of attribute collections.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def collections
  @collections
end

#configObject (readonly)

Returns the value of attribute config.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def config
  @config
end

#contextsObject (readonly)

Returns the value of attribute contexts.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def contexts
  @contexts
end

#envObject (readonly)

Returns the value of attribute env.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def env
  @env
end

#environmentsObject (readonly)

Returns the value of attribute environments.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def environments
  @environments
end

#formatterObject (readonly)

Returns the value of attribute formatter.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def formatter
  @formatter
end

#mixinsObject (readonly)

Returns the value of attribute mixins.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def mixins
  @mixins
end

#resourcesObject (readonly)

Returns the value of attribute resources.



1482
1483
1484
# File 'lib/spectre.rb', line 1482

def resources
  @resources
end

Class Method Details

.currentObject

The current used engine



1490
1491
1492
# File 'lib/spectre.rb', line 1490

def self.current
  @@current
end

.register(cls, *methods) ⇒ Object

Register a class and methods, which should be available in all spectre scopes



1498
1499
1500
# File 'lib/spectre.rb', line 1498

def self.register cls, *methods
  @@modules << [cls, methods]
end

Instance Method Details

#cleanupObject

Cleanup temporary files like logs, etc.



1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
# File 'lib/spectre.rb', line 1692

def cleanup
  Dir.chdir(@config['work_dir'])

  # Remove all log files explicitly
  log_file_pattern = @config['log_file'].gsub('<date>', '*')
  FileUtils.rm_rf(Dir.glob(log_file_pattern), secure: true)

  # Remove all files (reports) in the output directory
  out_files_pattern = File.join(@config['out_path'], '*')
  FileUtils.rm_rf(Dir.glob(out_files_pattern), secure: true)
end

#describe(desc) ⇒ Object

Describe a test subject



1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
# File 'lib/spectre.rb', line 1707

def describe(desc, &)
  file = caller
    .first
    .gsub(/:in .*/, '')
    .gsub(Dir.pwd, '.')

  DefinitionContext
    .new(desc, file, self)
    .instance_eval(&)
end

#list(config = @config) ⇒ Object

Get a list of specs with the configured filter



1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
# File 'lib/spectre.rb', line 1643

def list config = @config
  spec_filter = config['specs'] || []
  tag_filter = config['tags'] || []

  @contexts
    .flat_map(&:specs)
    .select do |spec|
      (spec_filter.empty? and tag_filter.empty?) or
        spec_filter.any? { |x| spec.name.match?("^#{x.gsub('*', '.*')}$") } or
        tag_filter.any? { |x| tag?(spec.tags, x) }
    end
end

#loggerObject

:nodoc:



1636
1637
1638
# File 'lib/spectre.rb', line 1636

def logger
  @logger ||= Logger.new(@config, progname: 'spectre')
end

#mixin(desc, params: [], &block) ⇒ Object

Registers a mixin



1721
1722
1723
1724
# File 'lib/spectre.rb', line 1721

def mixin desc, params: [], &block
  file, line = get_call_location(caller_locations)
  @mixins[desc] = Mixin.new(desc, params, block, file, line)
end

#report(runs) ⇒ Object

Create a report with the given runs and configured reporter.



1681
1682
1683
1684
1685
1686
1687
# File 'lib/spectre.rb', line 1681

def report runs
  @config['reporters'].each do |reporter|
    Object.const_get(reporter)
      .new(@config)
      .report(runs)
  end
end

#respond_to_missing?(method) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


1626
1627
1628
# File 'lib/spectre.rb', line 1626

def respond_to_missing?(method, *)
  @delegates.key? method
end

#runObject

Runs specs with the current config



1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
# File 'lib/spectre.rb', line 1659

def run
  @@modules.each do |mod, methods|
    target = mod.respond_to?(:new) ? mod.new(@config, logger) : mod

    methods.each do |method|
      @delegates[method] = target
    end
  end

  specs = list
  specs_set = Set.new(list)

  specs
    .group_by { |x| x.parent.root }
    .flat_map { |context, _| context.run(specs_set) }
rescue Interrupt
  # Do nothing here
end