ocrunner 0.3.2 → 0.4.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.
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