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
51
# 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_rows = 0
  @last_frame = nil
  @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



165
166
167
168
169
170
171
172
# File 'lib/parallel_specs/cli/dashboard.rb', line 165

def frame
  lines = if interactive?
    [header_line, *workers.map { |worker| worker_line(worker) }]
  else
    [plain_header_line]
  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



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

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?

        begin
          process_event(process_number, JSON.parse(line))
        rescue JSON::ParserError, KeyError => e
          warn "parallel_specs: dashboard event ignored while polling worker #{process_number + 1}=#{path}: #{e.class}: #{e.message}"
        end
      end
    end
  end
end

#process_event(process_number, event) ⇒ Object



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

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



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
79
# File 'lib/parallel_specs/cli/dashboard.rb', line 53

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 => e
      @running = false
      warn "parallel_specs: dashboard refresh failed while polling #{event_file_context}: #{e.class}: #{e.message}"
    end
  end
end

#stopObject



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

def stop
  @running = false
  @refresh_thread&.join

  @mutex.synchronize do
    begin
      poll_once
    rescue => 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



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

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



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

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