micron 0.5.0

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +19 -0
  3. data/Gemfile.lock +88 -0
  4. data/Rakefile +40 -0
  5. data/VERSION +1 -0
  6. data/bin/micron +4 -0
  7. data/lib/micron.rb +29 -0
  8. data/lib/micron/app.rb +127 -0
  9. data/lib/micron/app/options.rb +73 -0
  10. data/lib/micron/assertion.rb +10 -0
  11. data/lib/micron/fork_runner.rb +55 -0
  12. data/lib/micron/minitest.rb +45 -0
  13. data/lib/micron/proc_runner.rb +114 -0
  14. data/lib/micron/rake.rb +29 -0
  15. data/lib/micron/reporter.rb +30 -0
  16. data/lib/micron/reporter/console.rb +146 -0
  17. data/lib/micron/reporter/coverage.rb +37 -0
  18. data/lib/micron/runner.rb +95 -0
  19. data/lib/micron/runner/backtrace_filter.rb +39 -0
  20. data/lib/micron/runner/clazz.rb +45 -0
  21. data/lib/micron/runner/clazz19.rb +24 -0
  22. data/lib/micron/runner/debug.rb +22 -0
  23. data/lib/micron/runner/exception_info.rb +16 -0
  24. data/lib/micron/runner/fork_worker.rb +185 -0
  25. data/lib/micron/runner/forking_clazz.rb +40 -0
  26. data/lib/micron/runner/liveness_checker.rb +40 -0
  27. data/lib/micron/runner/liveness_checker/ping.rb +65 -0
  28. data/lib/micron/runner/liveness_checker/pong.rb +36 -0
  29. data/lib/micron/runner/method.rb +124 -0
  30. data/lib/micron/runner/parallel_clazz.rb +135 -0
  31. data/lib/micron/runner/proc_clazz.rb +48 -0
  32. data/lib/micron/runner/process_reaper.rb +98 -0
  33. data/lib/micron/runner/shim.rb +68 -0
  34. data/lib/micron/runner/test_file.rb +79 -0
  35. data/lib/micron/test_case.rb +36 -0
  36. data/lib/micron/test_case/assertions.rb +701 -0
  37. data/lib/micron/test_case/lifecycle_hooks.rb +74 -0
  38. data/lib/micron/test_case/redir_logging.rb +85 -0
  39. data/lib/micron/test_case/teardown_coverage.rb +13 -0
  40. data/lib/micron/util/ex.rb +23 -0
  41. data/lib/micron/util/io.rb +54 -0
  42. data/lib/micron/util/thread_dump.rb +29 -0
  43. data/micron.gemspec +97 -0
  44. metadata +184 -0
@@ -0,0 +1,39 @@
1
+
2
+ module Micron
3
+
4
+ # Default backtrace filter
5
+ #
6
+ # This can be swapped out by setting Micron::Runner.backtrace_filter
7
+ # to a block, proc, lambda, or a class with a #call method.
8
+ class BacktraceFilter
9
+
10
+ def call(bt)
11
+ return ["No backtrace"] unless bt and !bt.empty?
12
+
13
+ new_bt = []
14
+
15
+ unless $DEBUG then
16
+
17
+ if bt.first =~ %r{^/.*?/lib/micron/test_case/assertions.rb:\d+:in} then
18
+ # first line is an assertion, pop it off
19
+ bt.shift
20
+ end
21
+
22
+ bt.each do |line|
23
+ break if line =~ %r{(bin|lib)/micron}
24
+ new_bt << line
25
+ end
26
+
27
+ # make sure we didn't remove everything - if we did, the error was in our code
28
+ new_bt = bt.reject { |line| line =~ %r{(bin|lib)/micron} } if new_bt.empty?
29
+ new_bt = bt.dup if new_bt.empty?
30
+ else
31
+ new_bt = bt.dup
32
+ end
33
+
34
+ new_bt
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,45 @@
1
+
2
+ module Micron
3
+ class Runner
4
+ class Clazz
5
+
6
+ include Debug
7
+
8
+ attr_reader :name, :methods
9
+
10
+ def initialize(clazz, file)
11
+ @name = clazz.to_s
12
+ @file = file
13
+ @methods = test_methods.map { |m| Method.new(self, m) }
14
+ end
15
+
16
+ # Create a new instance of the Class represented by this object
17
+ def create
18
+ name_to_const.new
19
+ end
20
+
21
+ def run
22
+ methods.each do |method|
23
+ method.run
24
+ Micron.runner.report(:end_method, method)
25
+ end
26
+ end
27
+
28
+
29
+ private
30
+
31
+ def test_methods
32
+ create.public_methods.find_all { |m|
33
+ m.to_s =~ /^test_/
34
+ }
35
+ end
36
+
37
+ def name_to_const
38
+ Module.const_get(name)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+
45
+ require "micron/runner/clazz19" if RUBY_VERSION =~ /1.9/
@@ -0,0 +1,24 @@
1
+
2
+ module Micron
3
+ class Runner
4
+ class Clazz
5
+
6
+ private
7
+
8
+ # Compat fix for Ruby 1.9.x
9
+ def name_to_const
10
+
11
+ names = name.split('::')
12
+ names.shift if names.empty? || names.first.empty?
13
+
14
+ constant = Module
15
+ names.each do |name|
16
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
17
+ end
18
+
19
+ return constant
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module Micron
3
+ class Runner
4
+ module Debug
5
+
6
+ def debug(str="")
7
+
8
+ return
9
+
10
+ name = Thread.current[:name] || ""
11
+ if !name.empty? then
12
+ name = "[#{name} #{$$}] "
13
+ else
14
+ name = "[#{$$}]"
15
+ end
16
+
17
+ puts "#{name} #{str}"
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Micron
3
+ class Runner
4
+ class ExceptionInfo
5
+
6
+ attr_reader :name, :message, :backtrace
7
+
8
+ def initialize(ex)
9
+ @name = ex.class.to_s
10
+ @message = ex.message
11
+ @backtrace = ex.backtrace
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,185 @@
1
+
2
+ require "micron/runner/debug"
3
+ require "micron/runner/liveness_checker"
4
+
5
+ module Micron
6
+ class Runner
7
+
8
+ # Fork Worker
9
+ #
10
+ # @example
11
+ # fw = Micron::Runner::ForkWorker.new do
12
+ # system("ls")
13
+ # "sup"
14
+ # end
15
+ # fw.run.wait
16
+ # puts fw.stdout
17
+ # puts fw.result # => sup
18
+ #
19
+ class ForkWorker
20
+
21
+ include Debug
22
+
23
+ CHUNK_SIZE = 1024 * 16
24
+
25
+ attr_reader :pid, :context, :status
26
+
27
+ def initialize(context=nil, capture_stdout=true, capture_stderr=true, &block)
28
+ @context = context
29
+ @capture_stdout = capture_stdout
30
+ @capture_stderr = capture_stderr
31
+ @block = block
32
+ @done = false
33
+ end
34
+
35
+ def run(check_liveness=false)
36
+ @out, @err = IO.pipe, IO.pipe
37
+ @parent_read, @child_write = IO.pipe
38
+ @child_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
39
+
40
+ @liveness_checker = LivenessChecker.new if check_liveness
41
+
42
+ @pid = fork do
43
+
44
+ Thread.current[:name] = "worker"
45
+ Micron.trap_thread_dump()
46
+
47
+ # close unused readers in child
48
+ @out.first.close
49
+ @err.first.close
50
+ @parent_read.close
51
+
52
+ # redirect io
53
+ STDOUT.reopen(@out.last) if @capture_stdout
54
+ STDERR.reopen(@err.last) if @capture_stderr
55
+ STDOUT.sync = STDERR.sync = true
56
+
57
+ clean_parent_file_descriptors()
58
+
59
+ @liveness_checker.pong if check_liveness
60
+
61
+ # run
62
+ debug("calling block")
63
+ ret = @block.call()
64
+ debug("block returned")
65
+
66
+ # Pass result to parent via pipe
67
+ Marshal.dump(ret, @child_write)
68
+ @child_write.flush
69
+ debug("wrote result to pipe")
70
+
71
+ # cleanup
72
+ @out.last.close
73
+ @err.last.close
74
+ @child_write.close
75
+ end
76
+
77
+ # close unused writers in parent
78
+ @out.last.close
79
+ @err.last.close
80
+ @child_write.close
81
+
82
+ @liveness_checker.ping(self) if check_liveness
83
+
84
+ self
85
+ end
86
+
87
+ # Blocking wait for process to finish
88
+ #
89
+ # @return [self]
90
+ def wait
91
+ return self if @done
92
+
93
+ Process.wait(@pid)
94
+ @done = true
95
+ @status = $?
96
+ @liveness_checker.stop if @liveness_checker
97
+ self
98
+ end
99
+
100
+ # Blocking wait for process to finish
101
+ #
102
+ # @return [Process::Status]
103
+ def wait2
104
+ return @status if @done
105
+
106
+ pid, @status = Process.wait2(@pid)
107
+ @done = true
108
+ @liveness_checker.stop if @liveness_checker
109
+ return @status
110
+ end
111
+
112
+ # Non-blocking wait for process to finish
113
+ #
114
+ # @return [Process::Status] nil if not yet complete
115
+ def wait_nonblock
116
+ return @status if @done
117
+
118
+ pid, @status = Process.waitpid2(@pid, Process::WNOHANG)
119
+ if !@status.nil? then
120
+ @done = true
121
+ @liveness_checker.stop if @liveness_checker
122
+ end
123
+ return @status
124
+ end
125
+
126
+ def result
127
+ if !@done then
128
+ wait()
129
+ end
130
+ @result = Marshal.load(@parent_read)
131
+ @parent_read.close
132
+ @result
133
+ end
134
+
135
+ def stdout
136
+ return @stdout if !@stdout.nil?
137
+ @stdout = @out.first.read
138
+ @out.first.close
139
+ @stdout ||= ""
140
+ @stdout
141
+ end
142
+
143
+ def stderr
144
+ return @stderr if !@stderr.nil?
145
+ @stderr = @err.first.read
146
+ @err.first.close
147
+ @stderr ||= ""
148
+ @stderr
149
+ end
150
+
151
+ def out
152
+ @out.first
153
+ end
154
+
155
+ def err
156
+ @err.first
157
+ end
158
+
159
+
160
+ private
161
+
162
+
163
+ # Cleanup all FDs inherited from the parent. We don't need them and we
164
+ # may throw errors if they are left open. 8192 should be high enough.
165
+ def clean_parent_file_descriptors
166
+ # Don't clean $stdin, $stdout, $stderr (0-2) or our own pipes
167
+ keep = [ @child_write.to_i, @out.last.to_i, @err.last.to_i ]
168
+ keep += @liveness_checker.fds if @liveness_checker
169
+
170
+ 3.upto(8192) do |n|
171
+ if !keep.include? n then
172
+ begin
173
+ fd = IO.for_fd(n)
174
+ fd.close if fd
175
+ rescue
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+
182
+ end
183
+ end
184
+ end
185
+
@@ -0,0 +1,40 @@
1
+
2
+ module Micron
3
+ class Runner
4
+
5
+ # A Clazz implementation which forks before running each test method
6
+ class ForkingClazz < ParallelClazz
7
+
8
+ private
9
+
10
+ def spawn_test(method)
11
+ ForkWorker.new(method) {
12
+ $0 = "micron: method"
13
+ # ERR.puts "#{$0} (#{$$})"
14
+
15
+ EasyCov.start
16
+
17
+ Shim.wrap {
18
+ method.run
19
+ }
20
+
21
+ method
22
+ }.run(true)
23
+ end
24
+
25
+ # Collect the result data
26
+ #
27
+ # @param [Array] finished Completed pids & their associated methods
28
+ #
29
+ # @return [Array<Method>]
30
+ def collect_results(finished)
31
+ finished.map{ |f| collect_result(f) }
32
+ end
33
+
34
+ def collect_result(worker)
35
+ worker.result
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+
2
+ require "micron/runner/liveness_checker/ping"
3
+ require "micron/runner/liveness_checker/pong"
4
+
5
+ module Micron
6
+ class Runner
7
+
8
+ class LivenessChecker
9
+
10
+ include Debug
11
+
12
+ def initialize
13
+ @ping = IO.pipe
14
+ @pong = IO.pipe
15
+ end
16
+
17
+ def ping(pid)
18
+ @pinger = Ping.new(@pong.first, @ping.last, pid)
19
+ @ping.first.close
20
+ @pong.last.close
21
+ end
22
+
23
+ def pong
24
+ @ponger = Pong.new(@ping.first, @pong.last)
25
+ @ping.last.close
26
+ @pong.first.close
27
+ end
28
+
29
+ def stop
30
+ @pinger.thread.kill
31
+ end
32
+
33
+ def fds
34
+ (@ping + @pong).map { |f| f.to_i }
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+
2
+ require "timeout"
3
+
4
+ module Micron
5
+ class Runner
6
+ class LivenessChecker
7
+
8
+ class Ping
9
+
10
+ include Debug
11
+
12
+ attr_reader :thread
13
+
14
+ def initialize(reader, writer, worker)
15
+ @thread = Thread.new(reader, writer, worker) { |reader, writer, worker|
16
+
17
+ Thread.current[:name] = "pinger-#{worker.pid}"
18
+ last_pong = Hitimes::Interval.now
19
+
20
+ begin
21
+ writer.sync = true
22
+
23
+ while true
24
+ begin
25
+ writer.puts "ping"
26
+ debug "sent ping"
27
+
28
+ reply = Timeout.timeout(0.1) { reader.readline }
29
+
30
+ if "pong\n" == reply then
31
+ last_pong = Hitimes::Interval.now
32
+ debug "got pong response"
33
+ end
34
+
35
+ rescue Exception => ex
36
+ if worker.wait_nonblock then
37
+ # process exited, no need to do anything more
38
+ # debug "no pong in #{last_pong.to_f} sec, but process exited"
39
+ break
40
+ end
41
+
42
+ debug "no pong received: #{Micron.dump_ex(ex)}"
43
+ if last_pong.to_f > 5.0 then
44
+ debug "no pong in #{last_pong.to_f} sec! Unleash the reaper!!"
45
+ Process.kill(9, worker.pid)
46
+ break
47
+ end
48
+ end
49
+
50
+ sleep 0.1
51
+ end # while
52
+
53
+ rescue => ex
54
+ debug "caught: #{Micron.dump_ex(ex)}"
55
+ end
56
+
57
+ debug "ping thread exiting"
58
+ }
59
+ end
60
+
61
+ end # Ping
62
+
63
+ end
64
+ end
65
+ end