Class: Polyrun::Hooks

Inherits:
Object
  • Object
show all
Includes:
WorkerShell
Defined in:
lib/polyrun/hooks.rb,
lib/polyrun/hooks/dsl.rb,
lib/polyrun/hooks/worker_shell.rb,
lib/polyrun/hooks/worker_runner.rb

Overview

Shell and Ruby DSL hooks around parallel orchestration, named like RSpec lifecycle callbacks: before_suite / after_suite (+before(:suite)+ / after(:suite)), before_shard / after_shard (parent process, per partition index), before_worker / after_worker (inside each worker process, around the test command).

Configure under hooks: in polyrun.yml: shell strings, and/or ruby: path to a Ruby DSL file. Run manually: polyrun hook run <phase> (see CLI).

Orchestration respects POLYRUN_HOOKS_DISABLE=1 (run_phase_if_enabled); polyrun hook run always runs #run_phase.

Defined Under Namespace

Modules: Dsl, WorkerRunner, WorkerShell

Constant Summary collapse

PHASES =
%i[
  before_suite after_suite
  before_shard after_shard
  before_worker after_worker
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from WorkerShell

#build_worker_shell_script

Constructor Details

#initialize(raw) ⇒ Hooks

Returns a new instance of Hooks.

Parameters:

  • raw (Hash)

    hooks block from YAML



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/polyrun/hooks.rb', line 52

def initialize(raw)
  @ruby_file = extract_ruby_file(raw)
  h = {}
  raw.each do |k, v|
    next if %w[ruby ruby_file].include?(k.to_s)

    ck = canonical_key(k)
    next if ck.nil?

    h[ck] = v
  end
  @raw = h.freeze
  @ruby_registry_loaded = false
  @ruby_registry = nil
end

Instance Attribute Details

#ruby_fileObject (readonly)

Returns the value of attribute ruby_file.



27
28
29
# File 'lib/polyrun/hooks.rb', line 27

def ruby_file
  @ruby_file
end

Class Method Details

.disabled?Boolean

Returns:

  • (Boolean)


29
30
31
32
# File 'lib/polyrun/hooks.rb', line 29

def self.disabled?
  v = ENV["POLYRUN_HOOKS_DISABLE"].to_s.downcase
  %w[1 true yes].include?(v)
end

.from_config(cfg) ⇒ Object



41
42
43
44
# File 'lib/polyrun/hooks.rb', line 41

def self.from_config(cfg)
  raw = cfg.respond_to?(:hooks) ? cfg.hooks : {}
  new(raw.is_a?(Hash) ? raw : {})
end

.parse_phase(str) ⇒ Object

Maps CLI or YAML-style names (before_suite, “before(:suite)”) to a phase symbol or nil.



47
48
49
# File 'lib/polyrun/hooks.rb', line 47

def self.parse_phase(str)
  new({}).send(:canonical_key, str)
end

.suite_per_matrix_job?Boolean

When POLYRUN_SHARD_TOTAL is greater than 1 (ci-shard-run matrix), suite hooks are skipped by default; set POLYRUN_HOOKS_SUITE_PER_MATRIX_JOB=1 to run before_suite / after_suite on every matrix job (legacy).

Returns:

  • (Boolean)


36
37
38
39
# File 'lib/polyrun/hooks.rb', line 36

def self.suite_per_matrix_job?
  v = ENV["POLYRUN_HOOKS_SUITE_PER_MATRIX_JOB"].to_s.downcase
  %w[1 true yes].include?(v)
end

Instance Method Details

#commands_for(phase) ⇒ Array<String>

Parameters:

  • phase (Symbol)

Returns:

  • (Array<String>)


96
97
98
99
100
101
102
103
104
105
# File 'lib/polyrun/hooks.rb', line 96

def commands_for(phase)
  v = @raw[phase.to_sym]
  case v
  when nil then []
  when Array then v.map(&:to_s).map(&:strip).reject(&:empty?)
  else
    s = v.to_s.strip
    s.empty? ? [] : [s]
  end
end

#empty?Boolean

Returns:

  • (Boolean)


68
69
70
71
72
73
74
75
76
77
# File 'lib/polyrun/hooks.rb', line 68

def empty?
  no_shell = PHASES.all? { |p| commands_for(p).empty? }
  return false unless no_shell

  return true if @ruby_file.nil? || @ruby_file.to_s.strip.empty?
  return true unless File.file?(File.expand_path(@ruby_file, Dir.pwd))

  reg = ruby_registry
  reg.nil? || reg.empty?
end

#merge_worker_ruby_env(env) ⇒ Object

Merges POLYRUN_HOOKS_RUBY_FILE when a DSL file is configured (for worker ruby -e).



86
87
88
89
90
91
92
# File 'lib/polyrun/hooks.rb', line 86

def merge_worker_ruby_env(env)
  return env unless @ruby_file
  abs = File.expand_path(@ruby_file, Dir.pwd)
  return env unless File.file?(abs)

  env.merge("POLYRUN_HOOKS_RUBY_FILE" => abs)
end

#run_phase(phase, env) ⇒ Integer

Runs Ruby DSL blocks (if any), then shell commands for phase.

Returns:

  • (Integer)

    exit code (0 if no commands)



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/polyrun/hooks.rb', line 109

def run_phase(phase, env)
  return 0 unless PHASES.include?(phase.to_sym)

  merged = stringify_env_for_hook(env).merge(
    "POLYRUN_HOOK_PHASE" => phase.to_s,
    "POLYRUN_HOOK" => "1"
  )

  reg = ruby_registry
  if reg&.any?(phase)
    begin
      reg.run(phase, merged)
    rescue => e
      Polyrun::Log.warn "polyrun hooks: #{phase} ruby hook failed: #{e.class}: #{e.message}"
      return 1
    end
  end

  commands_for(phase).each do |cmd|
    ok = system(merged, "sh", "-c", cmd)
    return $?.exitstatus unless ok
  end
  0
end

#run_phase_if_enabled(phase, env) ⇒ Object

Like #run_phase, but no-ops when disabled? (+POLYRUN_HOOKS_DISABLE=1+). Used by run-shards / ci-shard orchestration.



135
136
137
138
139
# File 'lib/polyrun/hooks.rb', line 135

def run_phase_if_enabled(phase, env)
  return 0 if self.class.disabled?

  run_phase(phase, env)
end

#worker_hooks?Boolean

Returns:

  • (Boolean)


79
80
81
82
83
# File 'lib/polyrun/hooks.rb', line 79

def worker_hooks?
  return true if commands_for(:before_worker).any? || commands_for(:after_worker).any?

  !!ruby_registry&.worker_hooks?
end