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



1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
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
# File 'lib/spectre.rb', line 1473

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:



1602
1603
1604
# File 'lib/spectre.rb', line 1602

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

Instance Attribute Details

#collectionsObject (readonly)

Returns the value of attribute collections.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def collections
  @collections
end

#configObject (readonly)

Returns the value of attribute config.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def config
  @config
end

#contextsObject (readonly)

Returns the value of attribute contexts.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def contexts
  @contexts
end

#envObject (readonly)

Returns the value of attribute env.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def env
  @env
end

#environmentsObject (readonly)

Returns the value of attribute environments.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def environments
  @environments
end

#formatterObject (readonly)

Returns the value of attribute formatter.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def formatter
  @formatter
end

#mixinsObject (readonly)

Returns the value of attribute mixins.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def mixins
  @mixins
end

#resourcesObject (readonly)

Returns the value of attribute resources.



1453
1454
1455
# File 'lib/spectre.rb', line 1453

def resources
  @resources
end

Class Method Details

.currentObject

The current used engine



1461
1462
1463
# File 'lib/spectre.rb', line 1461

def self.current
  @@current
end

.register(cls, *methods) ⇒ Object

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



1469
1470
1471
# File 'lib/spectre.rb', line 1469

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

Instance Method Details

#cleanupObject

Cleanup temporary files like logs, etc.



1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
# File 'lib/spectre.rb', line 1662

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



1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
# File 'lib/spectre.rb', line 1677

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



1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
# File 'lib/spectre.rb', line 1614

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:



1607
1608
1609
# File 'lib/spectre.rb', line 1607

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

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

Registers a mixin



1691
1692
1693
1694
# File 'lib/spectre.rb', line 1691

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.



1651
1652
1653
1654
1655
1656
1657
# File 'lib/spectre.rb', line 1651

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

#respond_to_missing?(method) ⇒ Boolean

:nodoc:



1597
1598
1599
# File 'lib/spectre.rb', line 1597

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

#runObject

Runs specs with the current config



1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
# File 'lib/spectre.rb', line 1630

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

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