Class: ParallelSpecs::CLI::Dashboard

Inherits:
Object
  • Object
show all
Defined in:
lib/parallel_specs/cli/dashboard.rb

Defined Under Namespace

Classes: WorkerState

Constant Summary collapse

SPINNER =
['-', '\\', '|', '/'].freeze
PROGRESS_BAR_WIDTH =
24
REFRESH_INTERVAL =
0.1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(groups:, event_files:, output: $stdout, use_colors: true, mode: :interactive, now: -> { ParallelSpecs.now }, width: nil, refresh_interval: REFRESH_INTERVAL) ⇒ Dashboard

Returns a new instance of Dashboard.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/parallel_specs/cli/dashboard.rb', line 33

def initialize(groups:, event_files:, output: $stdout, use_colors: true, mode: :interactive, now: -> { ParallelSpecs.now }, width: nil, refresh_interval: REFRESH_INTERVAL)
  @workers = groups.each_with_index.map do |group, index|
    WorkerState.new(index + 1, group.size, nil, 0, 0, 0, nil, nil, nil, nil)
  end
  @event_files = event_files
  @output = output
  @use_colors = use_colors
  @mode = mode
  @now = now
  @width = width
  @refresh_interval = refresh_interval
  @mutex = Mutex.new
  @event_offsets = Hash.new(0)
  @event_remainders = Hash.new { |hash, key| hash[key] = +"" }
  @spinner_index = 0
  @rendered_lines = 0
  @dirty = true
end

Instance Attribute Details

#workersObject (readonly)

Returns the value of attribute workers.



27
28
29
# File 'lib/parallel_specs/cli/dashboard.rb', line 27

def workers
  @workers
end

Instance Method Details

#frameObject



160
161
162
163
164
165
166
167
# File 'lib/parallel_specs/cli/dashboard.rb', line 160

def frame
  lines = if interactive?
    [header_line, *workers.map { |worker| worker_line(worker) }]
  else
    [plain_header_line, *workers.map { |worker| plain_worker_line(worker) }]
  end
  "#{lines.join("\n")}\n"
end

#plain?Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/parallel_specs/cli/dashboard.rb', line 29

def plain?
  @mode == :plain
end

#poll_onceObject



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/parallel_specs/cli/dashboard.rb', line 137

def poll_once
  @event_files.each do |process_number, path|
    next unless File.exist?(path)

    File.open(path, 'r') do |file|
      file.seek(@event_offsets[process_number])
      chunk = file.read.to_s
      @event_offsets[process_number] = file.pos
      next if chunk.empty?

      buffer = @event_remainders[process_number] << chunk
      lines = buffer.split("\n", -1)
      @event_remainders[process_number] = lines.pop.to_s

      lines.each do |line|
        next if line.empty?

        process_event(process_number, JSON.parse(line))
      end
    end
  end
end

#process_event(process_number, event) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/parallel_specs/cli/dashboard.rb', line 114

def process_event(process_number, event)
  worker = @workers.fetch(process_number)
  worker.started_at ||= @now.call

  case event.fetch('event')
  when 'start'
    worker.example_total = event['total']
  when 'example_started'
    worker.current_example = event['example']
  when 'example_passed'
    worker.passed += 1
    worker.current_example = event['example']
  when 'example_pending'
    worker.pending += 1
    worker.current_example = event['example']
  when 'example_failed'
    worker.failed += 1
    worker.current_example = event['example']
  end

  @dirty = true
end

#startObject



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/parallel_specs/cli/dashboard.rb', line 52

def start
  @mutex.synchronize do
    @started_at = @now.call
    render if interactive?
  end

  return unless interactive?

  @running = true
  @refresh_thread = Thread.new do
    Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)

    begin
      while @running
        sleep @refresh_interval
        @mutex.synchronize do
          poll_once
          @spinner_index += 1
          render if @dirty
        end
      end
    rescue StandardError => e
      @running = false
      warn "parallel_specs: dashboard refresh failed while polling #{event_file_context}: #{e.class}: #{e.message}"
    end
  end
end

#stopObject



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/parallel_specs/cli/dashboard.rb', line 80

def stop
  @running = false
  @refresh_thread&.join

  @mutex.synchronize do
    begin
      poll_once
    rescue StandardError => e
      warn "parallel_specs: dashboard final poll failed while polling #{event_file_context}: #{e.class}: #{e.message}"
    end
    render
    @output.puts if interactive?
    @output.flush if @output.respond_to?(:flush)
  end
end

#worker_finished(process_number, exit_status:) ⇒ Object



104
105
106
107
108
109
110
111
112
# File 'lib/parallel_specs/cli/dashboard.rb', line 104

def worker_finished(process_number, exit_status:)
  synchronize do
    worker = @workers.fetch(process_number)
    worker.started_at ||= @now.call
    worker.finished_at = @now.call
    worker.exit_status = exit_status
    @dirty = true
  end
end

#worker_started(process_number) ⇒ Object



96
97
98
99
100
101
102
# File 'lib/parallel_specs/cli/dashboard.rb', line 96

def worker_started(process_number)
  synchronize do
    worker = @workers.fetch(process_number)
    worker.started_at ||= @now.call
    @dirty = true
  end
end