ocrunner 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -12,6 +12,7 @@ begin
12
12
  gem.authors = ["Jim Benton"]
13
13
  gem.add_dependency('trollop')
14
14
  gem.add_dependency('fssm')
15
+ gem.add_dependency('oniguruma')
15
16
  gem.executables = ['ocrunner']
16
17
  end
17
18
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.4.0
@@ -3,4 +3,6 @@ require File.dirname(__FILE__) + '/ocrunner/cli'
3
3
  require File.dirname(__FILE__) + '/ocrunner/test_case'
4
4
  require File.dirname(__FILE__) + '/ocrunner/test_error'
5
5
  require File.dirname(__FILE__) + '/ocrunner/test_suite'
6
+ require File.dirname(__FILE__) + '/ocrunner/parse_machine'
7
+ require File.dirname(__FILE__) + '/ocrunner/output_processor'
6
8
  require File.dirname(__FILE__) + '/ocrunner/test_runner'
@@ -31,7 +31,6 @@ module OCRunner
31
31
  opt :debug_command, "Print xcodebuild command and exit", :type => :boolean, :default => false
32
32
  opt :verbose, "Display all xcodebuild output after summary", :type => :boolean, :default => false
33
33
  opt :loud_compilation, "Always show verbose output when a compilation or linking error occurs", :type => :boolean, :default => true
34
- opt :oclog, "Display OCLog log messages", :type => :boolean, :default => true
35
34
  opt :oclog_help, "Print OCLog code example and exit", :type => :boolean, :default => false
36
35
  end
37
36
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  module OCRunner
4
4
  module Console
5
+
6
+ attr :output
7
+
5
8
  def colorize(text, color_code)
6
9
  "#{color_code}#{text.to_s}\033[0m"
7
10
  end
@@ -10,13 +13,51 @@ module OCRunner
10
13
  def green(text); colorize(text, "\033[32m"); end
11
14
  def blue(text); colorize(text, "\033[34m"); end
12
15
 
13
- def indent(text='')
14
- " " + text.to_s
16
+ def indent(*args)
17
+ if args.first.is_a? Numeric
18
+ indents = args.first
19
+ text = args.last
20
+ else
21
+ indents = 1
22
+ text = args.first
23
+ end
24
+ " " * indents + text.to_s
25
+ end
26
+
27
+ def execute(cmd, &block)
28
+ IO.popen("#{cmd} 2>&1") do |f|
29
+ while line = f.gets do
30
+ yield line
31
+ end
32
+ end
15
33
  end
34
+
16
35
  def present(&block)
17
36
  puts
18
37
  yield
19
38
  puts
20
39
  end
40
+
41
+ def clean_path(path)
42
+ return 'unknown' if path.nil?
43
+ @current_directory = Dir.pwd
44
+ path.gsub(@current_directory + '/', '')
45
+ end
46
+
47
+ def out(line = '')
48
+ @output ||= []
49
+ @output << line.rstrip
50
+ end
51
+
52
+ def growl(message)
53
+ if @options[:growl]
54
+ execute "growlnotify -i \"xcodeproj\" -m \"#{message}\"" do |error|
55
+ if error =~ /command not found/
56
+ out red('You must have growl and growl notify installed to enable growl support. See http://growl.info.')
57
+ end
58
+ end
59
+ end
60
+ end
61
+
21
62
  end
22
63
  end
@@ -0,0 +1,213 @@
1
+ module OCRunner
2
+ class OutputProcessor < ParseMachine
3
+
4
+ include Console
5
+
6
+ def initialize(out, options)
7
+ initialize_state
8
+ @passed = true
9
+ @suites = []
10
+ @log = ''
11
+ @out = out
12
+ @options = options
13
+ @compilation_error_occurred = false
14
+ end
15
+
16
+ def process_line(line)
17
+ @log << line unless line =~ /setenv/
18
+ process_console_output(line)
19
+ @out.flush
20
+ end
21
+
22
+ def build_error(message)
23
+ out red(message)
24
+ @passed = false
25
+ end
26
+
27
+ def build_failed
28
+ growl('BUILD FAILED!')
29
+ out red('*** BUILD FAILED ***')
30
+ end
31
+
32
+ def build_succeeded
33
+ growl('Build succeeded.')
34
+ out green('*** BUILD SUCCEEDED ***')
35
+ end
36
+
37
+ def display_results
38
+ @suites.each do |suite|
39
+ suite.failed_cases.each do |kase|
40
+ out indent red("[#{suite.name} #{kase.name}] FAILED")
41
+ kase.errors.each do |error|
42
+ out indent 2, "on line #{error.line} of #{clean_path(error.path)}:"
43
+ error.message.each_line do |line|
44
+ out indent 2, red(line.strip)
45
+ end
46
+ end
47
+ end
48
+ out if suite.failures?
49
+ end
50
+
51
+ @suites.each do |suite|
52
+ number = suite.failed_cases.size
53
+ out "Suite '#{suite.name}': #{suite.cases.size - number} passes and #{number} failures in #{suite.time} seconds."
54
+ end
55
+
56
+ out
57
+
58
+ if @passed
59
+ build_succeeded
60
+ else
61
+ build_failed
62
+ end
63
+
64
+ puts @log if @options[:verbose] || (@compilation_error_occurred && @options[:loud_compilation])
65
+ puts red "A compilation error occurred" if @compilation_error_occurred
66
+ puts @output.join("\n")
67
+ puts
68
+ end
69
+
70
+ def process_console_output(line)
71
+ process_input(line)
72
+ end
73
+
74
+ def compilation_error_occurred!
75
+ @compilation_error_occurred = true
76
+ end
77
+
78
+ default_state :ready
79
+
80
+ state :ready, {
81
+ :start_suite => :suite_running,
82
+ :fail_build => :build_failed,
83
+ :fail_without_project => :build_failed
84
+ }
85
+
86
+ state :suite_running, {
87
+ :start_case => :case_running,
88
+ :end_suite => :ready
89
+ }
90
+
91
+ state :case_running, {
92
+ :fail_case => :suite_running,
93
+ :pass_case => :suite_running,
94
+ :start_error => :recording_error,
95
+ :start_log => :recording_log,
96
+ :log_line => :case_running
97
+ }
98
+
99
+ state :recording_error, {
100
+ :fail_case => :suite_running,
101
+ :record_error => :recording_error,
102
+ :start_error => :recording_error
103
+ }
104
+
105
+ state :recording_log, {
106
+ :record_log => :recording_log,
107
+ :end_log => :case_running
108
+ }
109
+
110
+ state :build_failed, {
111
+ :record_build_log => :build_failed
112
+ }
113
+
114
+
115
+ # log a single entire line
116
+ match "[\\-|\\+](\\[.+\\]):(\\d+):(.+):(\033\\[35m.+\033\\[0m)"
117
+ event :log_line do |line, signature, line_number, file, log|
118
+ out
119
+ out indent blue("#{signature} logged on line #{line_number} of #{clean_path(file)}:")
120
+ out indent 2, log
121
+ end
122
+
123
+ # multiline log begin
124
+ match "[\\-|\\+](\\[.+\\]):(\\d+):(.+):(\033\\[35m.+)"
125
+ event :start_log do |line, signature, line_number, file, log|
126
+ out
127
+ out indent blue("#{signature} logged on line #{line_number} of #{clean_path(file)}:")
128
+ out indent 2, log
129
+ end
130
+
131
+ # multiline log end
132
+ match "\033\\[0m"
133
+ event :end_log do |line|
134
+ out indent 2, line
135
+ out
136
+ end
137
+
138
+ # multiline log body
139
+ match /.+/
140
+ event :record_log do |line|
141
+ out indent 2, line
142
+ end
143
+
144
+ match /Test Case '-\[.+ (.+)\]' started/
145
+ event :start_case do |line, case_name|
146
+ @current_case = TestCase.new(case_name)
147
+ @current_suite.cases << @current_case
148
+ end
149
+
150
+ match /Test Case .+ passed/
151
+ event :pass_case do
152
+ @current_case = nil
153
+ print(green('.'))
154
+ end
155
+
156
+ match /Test Case .+ failed/
157
+ event :fail_case do
158
+ print(red('.'))
159
+ @current_case.fail!
160
+ @passed = false
161
+ @current_case = nil
162
+ end
163
+
164
+ match /(.+\.m):(\d+): error: -\[(.+) (.+)\] :(?: (.+):?)?/
165
+ event :start_error do |line, file, line_number, klass, method, message|
166
+ @current_case.errors << TestError.new(file, line_number, message)
167
+ end
168
+
169
+ match /(.+)/
170
+ event :record_error do |line, message|
171
+ @current_case.errors.last.message << message + "\n"
172
+ end
173
+
174
+ match /Test Suite '([^\/]+)' started/
175
+ event :start_suite do |line, suite_name|
176
+ @current_suite = TestSuite.new(suite_name)
177
+ @suites << @current_suite
178
+ print "#{suite_name} "
179
+ end
180
+
181
+ match /^Executed.+\(([\d\.]+)\) seconds/
182
+ event :end_suite do |line, seconds|
183
+ @current_suite.time = seconds
184
+ print "\n" # clear console line
185
+ end
186
+
187
+ match /The executable for the test bundle at (.+\.octest) could not be found/
188
+ event :executable_not_found do |line, test_path|
189
+ build_error("Test executable #{clean_path(text_path)} could not be found")
190
+ end
191
+
192
+ match /.+\.m:\d+: error: .*/
193
+ match /".+", referenced from:/
194
+ match /-\[\w+ \w+\] in .+\.o/
195
+ match /Bus error/
196
+ match /Segmentation fault/
197
+ event :fail_build do |line|
198
+ compilation_error_occurred!
199
+ build_error(line)
200
+ end
201
+
202
+ match /does not contain an Xcode project/
203
+ event :fail_without_project do |line|
204
+ build_error('No Xcode project was found.')
205
+ end
206
+
207
+ match /.+/
208
+ event :record_build_log do |line|
209
+ @log << line
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,72 @@
1
+ require 'oniguruma'
2
+
3
+ module OCRunner
4
+ class ParseMachine
5
+
6
+ include Oniguruma
7
+
8
+ class << self
9
+ attr_accessor :events, :states
10
+ def match(regex)
11
+ @next_event ||= {}
12
+ @next_event[:regexes] ||= []
13
+ @next_event[:regexes] << regex
14
+ end
15
+ def event(name, options={}, &block)
16
+ @events ||= []
17
+ @events << @next_event.merge(
18
+ :name => name,
19
+ :options => options,
20
+ :callback => block
21
+ )
22
+ @next_event = nil
23
+ end
24
+ def state(name, transitions={})
25
+ @states ||= {}
26
+ @states[name] ||= {}
27
+ @states[name][:transitions] = transitions
28
+ end
29
+ def default_state(default_state)
30
+ @states ||= {}
31
+ @states[default_state] ||= {}
32
+ @states[default_state][:default] = true
33
+ end
34
+ end
35
+
36
+ def initialize_state
37
+ @state = default_state
38
+ raise "Default state not defined" if @state.nil?
39
+ end
40
+
41
+ def default_state
42
+ self.class.states.each_pair do |state_name, state_definition|
43
+ return state_name if state_definition[:default] == true
44
+ end
45
+ end
46
+
47
+ def event(name)
48
+ self.class.events.find do |event|
49
+ event[:name] == name
50
+ end
51
+ end
52
+
53
+ def process_input(line)
54
+ self.class.events.each do |event|
55
+ if self.class.states[@state][:transitions].has_key?(event[:name])
56
+ event[:regexes].each do |regex|
57
+ if regex.is_a?(String)
58
+ regex = ORegexp.new(regex)
59
+ end
60
+ if (match = regex.match(line))
61
+ args = [line] + match[1..-1]
62
+ self.instance_exec(*args, &event[:callback]) if event[:callback]
63
+ @state = self.class.states[@state][:transitions][event[:name]]
64
+ return
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -1,6 +1,6 @@
1
1
  module OCRunner
2
2
 
3
- # Container for test failures info
3
+ # Container for test failure info
4
4
 
5
5
  class TestError < Struct.new(:path, :line, :message)
6
6
  end
@@ -3,28 +3,14 @@ module OCRunner
3
3
  include Console
4
4
 
5
5
  class BuildFailure < StandardError; end
6
-
7
- attr_reader :compilation_error_occurred
8
6
 
9
7
  def initialize(options)
10
- @suites = []
11
- @log = ''
12
- @current_directory = Dir.pwd
13
8
  @options = options
14
- @passed = true
15
- @compilation_error_occurred = false
16
- @output = []
9
+ @processor = OutputProcessor.new($stdout, @options)
17
10
 
18
11
  build_command
19
12
  setup
20
13
  run_tests
21
- summarize
22
- display_results
23
- end
24
-
25
- def setup
26
- puts "-"*80
27
- puts
28
14
  end
29
15
 
30
16
  def build_command
@@ -37,195 +23,18 @@ module OCRunner
37
23
  exit
38
24
  end
39
25
  end
40
-
41
- def run_tests
42
-
43
- puts "ocrunner started. control-c to exit, control-\\ to toggle verbosity\n\n"
44
-
45
- execute @command do |line|
46
- @log << line unless line =~ /setenv/
47
- process_console_output(line)
48
- $stdout.flush
49
- end
50
- end
51
-
52
- def summarize
53
-
54
- @suites.each do |suite|
55
- suite.failed_cases.each do |kase|
56
- out ' ' + red("[#{suite.name} #{kase.name}] FAIL")
57
- kase.errors.each do |error|
58
- out ' ' + red(error.message) + " line #{error.line} of #{clean_path(error.path)}"
59
- end
60
- end
61
- out if suite.failures?
62
- end
63
-
64
- @suites.each do |suite|
65
- number = suite.failed_cases.size
66
- out "Suite '#{suite.name}': #{suite.cases.size - number} passes and #{number} failures in #{suite.time} seconds."
67
- end
68
-
69
- out
70
-
71
- if @passed
72
- build_succeeded
73
- else
74
- build_failed
75
- end
76
- end
77
26
 
78
- def display_results
79
- puts @log if @options[:verbose] || (compilation_error_occurred && @options[:loud_compilation])
80
- puts @output.join("\n")
27
+ def setup
28
+ puts "-"*80
81
29
  puts
82
- end
83
-
84
- def build_error(message)
85
- out red(message)
86
- @passed = false
87
- end
88
-
89
- def build_failed
90
- growl('BUILD FAILED!')
91
- out red('*** BUILD FAILED ***')
92
- end
93
-
94
- def build_succeeded
95
- growl('Build succeeded.')
96
- out green('*** BUILD SUCCEEDED ***')
97
- end
98
-
99
- def process_console_output(line)
100
-
101
- if @options[:oclog]
102
- if line.include?("\033\[35m")
103
- line =~ /[\-|\+](\[.+\]):(\d+):(.+):/
104
- out blue("#{$1} on line #{$2} of #{clean_path($3)}:")
105
- out line.slice(line.index("\033\[35m")..-1)
106
- @debug_output = true unless line.include?("\033[0m")
107
- return
108
- end
109
-
110
- if line.include?("\033[0m")
111
- @debug_output = false
112
- out line
113
- out
114
- return
115
- end
116
-
117
- if @debug_output
118
- out line
119
- return
120
- end
121
- end
122
-
123
- # test case started
124
- if line =~ /Test Case '-\[.+ (.+)\]' started/
125
- @current_case = TestCase.new($1)
126
- @current_suite.cases << @current_case
127
- end
128
-
129
- # test case passed
130
- if line =~ /Test Case .+ passed/
131
- @current_case = nil
132
- print(green('.'))
133
- end
134
-
135
- # test failure
136
- if line =~ /(.+\.m):(\d+): error: -\[(.+) (.+)\] :(?: (.+):?)?/
137
- @current_case.errors << TestError.new($1, $2, $5)
138
- test_failure_occurred!
139
- end
140
-
141
- # start test suite
142
- if line =~ /Test Suite '([^\/]+)' started/
143
- @current_suite = TestSuite.new($1)
144
- @suites << @current_suite
145
- print "#{$1} "
146
- end
147
-
148
- # finish test suite
149
- if @current_suite && line =~ /^Executed/ && line =~ /\(([\d\.]+)\) seconds/
150
- @current_suite.time = $1
151
- print "\n" # clear console line
152
- end
153
-
154
- # test executable not found
155
- if line =~ /The executable for the test bundle at (.+\.octest) could not be found/
156
- build_error("Test executable #{clean_path($1)} could not be found")
157
- end
158
-
159
- # compilation errors
160
- if !@current_case && line =~ /(.+\.m):(\d+): error: (.*)/
161
- compilation_error_occurred!
162
- build_error($&)
163
- end
164
-
165
- # compilation reference error
166
- if line =~ /"(.+)", referenced from:/
167
- compilation_error_occurred!
168
- build_error($&)
169
- end
170
-
171
- # linking error
172
- if line =~ /-\[\w+ \w+\] in .+\.o/
173
- compilation_error_occurred!
174
- build_error($&)
175
- end
176
-
177
- # bus error
178
- if line =~ /Bus error/
179
- build_error('Bus error while running tests.')
180
- end
181
-
182
- # segfault
183
- if line =~ /Segmentation fault/
184
- build_error('Segmentation fault while running tests.')
185
- end
186
-
187
- # no Xcode project found
188
- if line =~ /does not contain an Xcode project/
189
- build_error('No Xcode project was found.')
190
- end
191
-
192
- end
193
-
194
- def compilation_error_occurred!
195
- @compilation_error_occurred = true
196
- end
197
-
198
- def test_failure_occurred!
199
- print red('.') if @current_case.passed? # only print a dot for the case's first failure
200
- @current_case.fail!
201
- @passed = false
202
- end
203
-
204
- def out(line = '')
205
- @output << line.rstrip
206
- end
207
-
208
- def clean_path(path)
209
- return 'unknown' if path.nil?
210
- path.gsub(@current_directory + '/', '')
211
- end
212
-
213
- def growl(message)
214
- if @options[:growl]
215
- execute "growlnotify -i \"xcodeproj\" -m \"#{message}\"" do |error|
216
- if error =~ /command not found/
217
- out red('You must have growl and growl notify installed to enable growl support. See http://growl.info.')
218
- end
219
- end
220
- end
30
+ puts "ocrunner started. control-c to exit, control-\\ to toggle verbosity\n\n"
221
31
  end
222
32
 
223
- def execute(cmd, &block)
224
- IO.popen("#{cmd} 2>&1") do |f|
225
- while line = f.gets do
226
- yield line
227
- end
33
+ def run_tests
34
+ execute @command do |line|
35
+ @processor.process_line(line)
228
36
  end
37
+ @processor.display_results
229
38
  end
230
39
 
231
40
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ocrunner}
8
- s.version = "0.3.2"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jim Benton"]
12
- s.date = %q{2010-04-23}
12
+ s.date = %q{2010-05-06}
13
13
  s.default_executable = %q{ocrunner}
14
14
  s.description = %q{Provides pretty console output for running OCUnit tests with xcodebuilder from the command line}
15
15
  s.email = %q{jim@autonomousmachine.com}
@@ -29,6 +29,8 @@ Gem::Specification.new do |s|
29
29
  "lib/ocrunner.rb",
30
30
  "lib/ocrunner/cli.rb",
31
31
  "lib/ocrunner/console.rb",
32
+ "lib/ocrunner/output_processor.rb",
33
+ "lib/ocrunner/parse_machine.rb",
32
34
  "lib/ocrunner/test_case.rb",
33
35
  "lib/ocrunner/test_error.rb",
34
36
  "lib/ocrunner/test_runner.rb",
@@ -54,13 +56,16 @@ Gem::Specification.new do |s|
54
56
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
57
  s.add_runtime_dependency(%q<trollop>, [">= 0"])
56
58
  s.add_runtime_dependency(%q<fssm>, [">= 0"])
59
+ s.add_runtime_dependency(%q<oniguruma>, [">= 0"])
57
60
  else
58
61
  s.add_dependency(%q<trollop>, [">= 0"])
59
62
  s.add_dependency(%q<fssm>, [">= 0"])
63
+ s.add_dependency(%q<oniguruma>, [">= 0"])
60
64
  end
61
65
  else
62
66
  s.add_dependency(%q<trollop>, [">= 0"])
63
67
  s.add_dependency(%q<fssm>, [">= 0"])
68
+ s.add_dependency(%q<oniguruma>, [">= 0"])
64
69
  end
65
70
  end
66
71
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 2
9
- version: 0.3.2
7
+ - 4
8
+ - 0
9
+ version: 0.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jim Benton
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-23 00:00:00 -05:00
17
+ date: 2010-05-06 00:00:00 -05:00
18
18
  default_executable: ocrunner
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -41,6 +41,18 @@ dependencies:
41
41
  version: "0"
42
42
  type: :runtime
43
43
  version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: oniguruma
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :runtime
55
+ version_requirements: *id003
44
56
  description: Provides pretty console output for running OCUnit tests with xcodebuilder from the command line
45
57
  email: jim@autonomousmachine.com
46
58
  executables:
@@ -61,6 +73,8 @@ files:
61
73
  - lib/ocrunner.rb
62
74
  - lib/ocrunner/cli.rb
63
75
  - lib/ocrunner/console.rb
76
+ - lib/ocrunner/output_processor.rb
77
+ - lib/ocrunner/parse_machine.rb
64
78
  - lib/ocrunner/test_case.rb
65
79
  - lib/ocrunner/test_error.rb
66
80
  - lib/ocrunner/test_runner.rb