utils 0.2.4 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,112 +2,171 @@ require 'tins/terminal'
2
2
  require 'term/ansicolor'
3
3
 
4
4
  begin
5
- begin
6
- require 'rspec'
7
- rescue LoadError
8
- end
9
- require 'rspec/core/formatters/base_text_formatter'
10
- rescue LoadError
11
- end
12
-
13
- if defined?(RSpec) && defined?(RSpec::Core::Formatters::BaseTextFormatter)
5
+ require 'rspec/core'
6
+ require 'rspec/core/formatters'
7
+ rescue LoadError => e
8
+ $DEBUG and warn "Caught #{e.class}: #{e}"
9
+ else
14
10
  module Utils
15
- class LineFormatter < RSpec::Core::Formatters::BaseTextFormatter
16
- def start(example_count)
17
- super
18
- filename = 'errors.lst'
19
- output.puts "Storing error list in #{filename.inspect}: "
20
- @errors_lst = File.new(filename, 'w')
21
- @errors_lst.sync = true
22
- end
23
-
24
- def close
25
- super
26
- @errors_lst.puts "\n#{?= * 75}\nFinished in #{format_duration(duration)}\n"
27
- @errors_lst.puts summary_line(example_count, failure_count, pending_count)
28
- @errors_lst.close
29
- end
30
-
31
- def example_passed(example)
32
- super
33
- output.puts format_line(example)
34
- end
35
-
36
- def example_pending(example)
37
- super
38
- output.puts format_line(example)
39
- end
40
-
41
- def example_failed(example)
42
- super
43
- dump_line_to_error_file(example)
44
- output.puts format_line(example)
45
- dump_failure_info(example)
46
- end
47
-
48
- def dump_failures
49
- end
50
-
51
- def dump_pending
52
- end
53
-
54
- def dump_commands_to_rerun_failed_examples
55
- end
56
-
57
- private
58
-
59
- def dump_failure_for_error_file(example)
60
- result = ''
61
- exception = example.execution_result[:exception]
62
- exception_class_name = exception_class_name_for(exception)
63
- result << "#{long_padding}Failure/Error: #{read_failed_line(exception, example).strip}\n"
64
- result << "#{long_padding}#{exception_class_name}:\n" unless exception_class_name =~ /RSpec/
65
- exception.message.to_s.split("\n").each { |line| result << "#{long_padding} #{line}\n" } if exception.message
66
- result
67
- end
68
-
69
- def dump_line_to_error_file(example)
70
- @errors_lst.flock File::LOCK_EX
71
- @errors_lst.puts "%s\n%3.3fs %s\n%s\n%s" % [
72
- location(example), run_time(example), example.full_description,
73
- Term::ANSIColor.uncolored(dump_failure_for_error_file(example)),
74
- (%w[ {{{ ] +
75
- format_backtrace(example.execution_result[:exception].backtrace, example) +
76
- %w[ }}} ]) * ?\n
77
- ]
78
- ensure
79
- @errors_lst.flock File::LOCK_UN
80
- end
81
-
82
- def run_time(example)
83
- example.execution_result[:run_time]
84
- end
85
-
86
- def format_line(example)
87
- description =
88
- if ENV['VERBOSE'].to_i == 1
89
- example.full_description
11
+ class LineFormatter
12
+ ::RSpec::Core::Formatters.register self, :start, :close,
13
+ :example_passed, :example_pending, :example_failed, :dump_summary
14
+
15
+ def initialize(output)
16
+ @output = output
17
+ @output.sync = true
18
+ filename = 'errors.lst'
19
+ @errors_lst = File.new(filename, 'w')
20
+ @errors_lst.sync = true
21
+ end
22
+
23
+ attr_reader :output
24
+
25
+ def start(_ignore)
26
+ output.puts "Storing error list in #{@errors_lst.path.inspect}: "
27
+ output.puts ?- * Tins::Terminal.columns
28
+ end
29
+
30
+ def close(_ignore)
31
+ @errors_lst.close
32
+ end
33
+
34
+ def dump_summary(summary)
35
+ line = summary_line(summary)
36
+ @errors_lst.puts ?= * 80, line
37
+ output.puts ?= * Tins::Terminal.columns, line
38
+ end
39
+
40
+ def example_passed(example)
41
+ output.puts format_line(example)
42
+ end
43
+
44
+ def example_pending(example)
45
+ output.puts format_line(example)
46
+ end
47
+
48
+ def example_failed(example)
49
+ dump_failure_to_error_file(example)
50
+ output.puts format_line(example)
51
+ dump_failure(example)
52
+ end
53
+
54
+ private
55
+
56
+ def summary_line(summary)
57
+ failure_percentage = 100 * summary.failure_count.to_f / summary.example_count
58
+ failure_percentage.nan? and failure_percentage = 0.0
59
+ pending_percentage = 100 * summary.pending_count.to_f / summary.example_count
60
+ pending_percentage.nan? and pending_percentage = 0.0
61
+ "%u of %u (%.2f %%) failed, %u pending (%.2f %%) in %.3f seconds" % [
62
+ summary.failure_count,
63
+ summary.example_count,
64
+ failure_percentage,
65
+ summary.pending_count,
66
+ pending_percentage,
67
+ summary.duration,
68
+ ]
69
+ end
70
+
71
+ def dump_failure(example)
72
+ output.puts(
73
+ description(example, full: true),
74
+ dump_failure_for_example(example)
75
+ )
76
+ end
77
+
78
+ def read_failed_line(example)
79
+ ''.strip
80
+ end
81
+
82
+ def dump_failure_for_example(example)
83
+ result = ''
84
+ exception = execution_result(example).exception
85
+ exception_class_name = exception.class.name
86
+ result << "Failure/Error: #{read_failed_line(example)}\n"
87
+ result << "#{exception_class_name}:\n" unless exception_class_name =~ /RSpec/
88
+ if m = exception.message
89
+ m.to_s.split("\n").each { |line| result << " #{line}\n" }
90
+ end
91
+ result
92
+ end
93
+
94
+ def format_backtrace(example, folding: false, limit: nil)
95
+ backtrace = execution_result(example).exception.backtrace
96
+ backtrace.nil? and return ''
97
+ if limit
98
+ backtrace = backtrace[0, limit]
99
+ end
100
+ result = []
101
+ folding and result << '{{{'
102
+ for line in backtrace
103
+ result << RSpec::Core::Metadata::relative_path(line)
104
+ end
105
+ folding and result << '}}}'
106
+ result * ?\n
107
+ end
108
+
109
+ def dump_failure_to_error_file(example)
110
+ @errors_lst.flock File::LOCK_EX
111
+ @errors_lst.puts "%s\n%3.3fs %s\n%s\n%s" % [
112
+ location(example), run_time(example), description(example, full: true),
113
+ dump_failure_for_example(example), format_backtrace(example, folding: true)
114
+ ]
115
+ ensure
116
+ @errors_lst.flock File::LOCK_UN
117
+ end
118
+
119
+ def execution_result(example)
120
+ example.example.metadata[:execution_result]
121
+ end
122
+
123
+ def description(example, full: ENV['VERBOSE'].to_i == 1)
124
+ if full
125
+ example.example.full_description
126
+ else
127
+ example.example.description
128
+ end
129
+ end
130
+
131
+ def run_time(example)
132
+ execution_result(example).run_time
133
+ end
134
+
135
+ def format_line(example)
136
+ args = [ location(example), run_time(example), description(example) ]
137
+ uncolored = "%s # S %3.3fs %s" % args
138
+ uncolored = uncolored[0, Tins::Terminal.columns]
139
+ case execution_result(example).status
140
+ when :passed
141
+ success_color(uncolored)
142
+ when :failed
143
+ failure_color(uncolored)
144
+ when :pending
145
+ pending_color(uncolored)
90
146
  else
91
- example.description
147
+ uncolored % args
92
148
  end
93
- args = [ location(example), run_time(example), description ]
94
- uncolored = "%s # S %3.3fs %s" % args
95
- uncolored = uncolored[0, Tins::Terminal.columns]
96
- case example.execution_result[:status]
97
- when 'passed'
98
- success_color(uncolored)
99
- when 'failed'
100
- failure_color(uncolored)
101
- when 'pending'
102
- pending_color(uncolored)
103
- else
104
- uncolored % args
105
- end
106
- end
107
-
108
- def location(example)
109
- RSpec::Core::Metadata::relative_path(example.location)
110
- end
149
+ end
150
+
151
+ def success_color(text)
152
+ Term::ANSIColor.green(text)
153
+ end
154
+
155
+ def failure_color(text)
156
+ Term::ANSIColor.red(text)
157
+ end
158
+
159
+ def pending_color(text)
160
+ Term::ANSIColor.yellow(text)
161
+ end
162
+
163
+ def location(example)
164
+ location = example.example.metadata[:location]
165
+ unless location.include?(?/)
166
+ location = example.example.metadata[:example_group][:location]
167
+ end
168
+ RSpec::Core::Metadata::relative_path(location)
169
+ end
111
170
  end
112
171
  end
113
172
  end
@@ -9,6 +9,8 @@ module Utils
9
9
  @pattern = @pattern.gsub(/[^#{@cset}]/, '') if @cset
10
10
  end
11
11
 
12
+ attr_reader :matcher
13
+
12
14
  def method_missing(*a, &b)
13
15
  @matcher.__send__(*a, &b)
14
16
  end
@@ -7,19 +7,9 @@ end
7
7
  module Utils
8
8
  class ProbeServer
9
9
  class Job
10
-
11
- class << self
12
- attr_writer :colorize
13
-
14
- def colorize?
15
- !!@colorize
16
- end
17
- end
18
- self.colorize = false
19
-
20
10
  def initialize(probe_server, args)
21
11
  @id = probe_server.next_job_id
22
- @args = args
12
+ @args = Array(args)
23
13
  end
24
14
 
25
15
  attr_reader :id
@@ -37,18 +27,15 @@ module Utils
37
27
  end
38
28
 
39
29
  def ok_colorize(string)
40
- return string unless self.class.colorize?
41
30
  case @ok
42
31
  when false then string.white.on_red
43
32
  when true then string.black.on_green
44
- else string.black.on_yellow
33
+ else string
45
34
  end
46
35
  end
47
36
 
48
37
  def inspect
49
- ok_colorize(
50
- "#<Job id=#{id} args=#{args.inspect} ok=#{ok}>"
51
- )
38
+ ok_colorize("Job##{id} #{args.map { |a| a.include?(' ') ? a.inspect : a } * ' '}")
52
39
  end
53
40
 
54
41
  alias to_s inspect
@@ -62,6 +49,12 @@ module Utils
62
49
  Thread.new { work_loop }
63
50
  end
64
51
 
52
+ def print(*msg)
53
+ if msg.first !~ /^irb: warn: can't alias / # shut your god d*mn wh*re mouth
54
+ super
55
+ end
56
+ end
57
+
65
58
  def start
66
59
  output_message "Starting probe server listening to #{@uri.inspect}.", type: :info
67
60
  DRb.start_service(@uri, self)
@@ -69,7 +62,8 @@ module Utils
69
62
  DRb.thread.join
70
63
  rescue Interrupt
71
64
  ARGV.clear << '-f'
72
- output_message %{\nEntering interactive mode: Type "commands" to get help for the commands.}, type: :info
65
+ output_message %{\nEntering interactive mode.}, type: :info
66
+ help
73
67
  begin
74
68
  old, $VERBOSE = $VERBOSE, nil
75
69
  examine(self)
@@ -88,40 +82,24 @@ module Utils
88
82
 
89
83
  annotate :doc
90
84
 
91
- def commands
92
- annotations = self.class.doc_annotations.sort_by(&:first)
93
- max_size = annotations.map { |a| a.first.size }.max
94
- output_message annotations.map { |n, v| "#{n.to_s.ljust(max_size + 1)}#{v}" }
95
- end
85
+ annotate :shortcut
96
86
 
97
- doc 'Pause processing of the job queue.'
98
- def pause
99
- mutex.lock
100
- true
101
- rescue ThreadError
102
- false
103
- end
104
-
105
- doc 'Continue processing of the job queue.'
106
- def continue
107
- mutex.unlock
108
- true
109
- rescue ThreadError
110
- false
111
- end
112
-
113
- doc 'Return the currently running job.'
114
- def job
115
- queue_synchronize do
116
- @job
117
- end
118
- end
119
-
120
- def next_job_id
121
- @current_job_id += 1
87
+ doc 'Display this help.'
88
+ shortcut :h
89
+ def help
90
+ docs = doc_annotations.sort_by(&:first)
91
+ docs_size = docs.map { |a| a.first.size }.max
92
+ format = '%-20s %-3s %s'
93
+ output_message [
94
+ (format % %w[ command sho description ]).on_color(20).white
95
+ ] << docs.map { |cmd, doc|
96
+ shortcut = shortcut_of(cmd) and shortcut = "(#{shortcut})"
97
+ format % [ cmd, shortcut, doc ]
98
+ }
122
99
  end
123
100
 
124
101
  doc 'Enqueue a new job with the argument array <job_args>.'
102
+ shortcut :e
125
103
  def job_enqueue(job_args)
126
104
  job = Job.new(self, job_args)
127
105
  output_message " → #{job.inspect} enqueued.", type: :info
@@ -130,35 +108,15 @@ module Utils
130
108
  alias enqueue job_enqueue
131
109
 
132
110
  doc 'Send the <signal> to the process that is working on the current job, if any.'
133
- def job_kill(signal = :TERM)
134
- @pid and Process.kill signal, @pid
135
- end
136
-
137
- doc 'Shutdown the server.'
111
+ doc 'Quit the server.'
112
+ shortcut :q
138
113
  def shutdown
139
114
  output_message "Server was shutdown down – HARD!", type: :warn
140
- exit! 23
141
- end
142
-
143
- doc 'List the currently pending jobs waiting to be run.'
144
- def jobs_list
145
- output_message @jobs_queue.instance_variable_get(:@que)
146
- end
147
-
148
- doc 'Clear all pending jobs.'
149
- def jobs_clear
150
- queue_synchronize do
151
- unless @jobs_queue.empty?
152
- @jobs_queue.clear
153
- output_message "Cleared all queued jobs.", type: :warn
154
- true
155
- else
156
- false
157
- end
158
- end
115
+ exit 23
159
116
  end
160
117
 
161
118
  doc 'Repeat the job with <job_id> or the last, it will be assigned a new id, though.'
119
+ shortcut :r
162
120
  def job_repeat(job_id = @history.last)
163
121
  Job === job_id and job_id = job_id.id
164
122
  if old_job = @history.find { |job| job.id == job_id }
@@ -170,6 +128,7 @@ module Utils
170
128
  end
171
129
 
172
130
  doc 'List the history of run jobs.'
131
+ shortcut :l
173
132
  def history_list
174
133
  output_message @history
175
134
  end
@@ -180,19 +139,39 @@ module Utils
180
139
  true
181
140
  end
182
141
 
142
+ class LogWrapper < BasicObject
143
+ def initialize(server, object)
144
+ @server, @object = server, object
145
+ end
146
+
147
+ def []=(name, value)
148
+ name, value = name.to_s, value.to_s
149
+ @server.output_message("Setting #{name}=#{value.inspect}.", type: :info)
150
+ @object[name] = value
151
+ end
152
+
153
+ def method_missing(*a, &b)
154
+ @object.__send__(*a, &b)
155
+ end
156
+ end
157
+
183
158
  doc "The environment of the server process, use env['a'] = 'b' and env['a']."
184
- def env
185
- ENV
159
+ memoize_method def env
160
+ LogWrapper.new(self, ENV)
186
161
  end
187
162
 
188
- private
163
+ doc "Clear the terminal screen"
164
+ shortcut :c
165
+ def clear
166
+ system "clear"
167
+ end
189
168
 
190
- def mutex
191
- @jobs_queue.instance_variable_get(:@mutex)
169
+ for (method_name, shortcut) in shortcut_annotations
170
+ alias_method shortcut, method_name
192
171
  end
193
172
 
194
- def queue_synchronize(&block)
195
- mutex.synchronize(&block)
173
+ def next_job_id
174
+ @current_job_id += 1
196
175
  end
197
176
 
198
177
  def output_message(msg, type: nil)
@@ -204,7 +183,7 @@ module Utils
204
183
  when :info
205
184
  msg.on_color(20).white
206
185
  when :warn
207
- msg.on_color(40).white
186
+ msg.on_color(94).white
208
187
  when :failure
209
188
  msg.on_color(124).blink.white
210
189
  else
@@ -215,10 +194,11 @@ module Utils
215
194
  self
216
195
  end
217
196
 
197
+ private
198
+
218
199
  def run_job(job)
219
- @pid = fork { exec(*cmd(job.args)) }
220
- output_message " → #{job.inspect} now running with pid #@pid.", type: :info
221
- Process.wait @pid
200
+ output_message " #{job.inspect} now running.", type: :info
201
+ system *cmd(job.args)
222
202
  message = " → #{job.inspect} was just run"
223
203
  if $?.success?
224
204
  job.ok = true