wycats-merb-core 0.9.8 → 0.9.9

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 (58) hide show
  1. data/CHANGELOG +136 -2
  2. data/CONTRIBUTORS +6 -0
  3. data/PUBLIC_CHANGELOG +15 -0
  4. data/Rakefile +12 -14
  5. data/lib/merb-core.rb +82 -43
  6. data/lib/merb-core/bootloader.rb +268 -60
  7. data/lib/merb-core/config.rb +119 -34
  8. data/lib/merb-core/controller/abstract_controller.rb +58 -18
  9. data/lib/merb-core/controller/exceptions.rb +2 -15
  10. data/lib/merb-core/controller/merb_controller.rb +28 -1
  11. data/lib/merb-core/controller/mime.rb +4 -0
  12. data/lib/merb-core/controller/mixins/controller.rb +14 -17
  13. data/lib/merb-core/controller/mixins/render.rb +23 -28
  14. data/lib/merb-core/controller/mixins/responder.rb +0 -1
  15. data/lib/merb-core/controller/template.rb +44 -20
  16. data/lib/merb-core/core_ext/kernel.rb +8 -3
  17. data/lib/merb-core/dispatch/default_exception/default_exception.rb +1 -1
  18. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +3 -1
  19. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +71 -67
  20. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +6 -2
  21. data/lib/merb-core/dispatch/dispatcher.rb +5 -9
  22. data/lib/merb-core/dispatch/request.rb +46 -57
  23. data/lib/merb-core/dispatch/router.rb +83 -6
  24. data/lib/merb-core/dispatch/router/behavior.rb +87 -27
  25. data/lib/merb-core/dispatch/router/resources.rb +281 -167
  26. data/lib/merb-core/dispatch/router/route.rb +141 -27
  27. data/lib/merb-core/logger.rb +213 -202
  28. data/lib/merb-core/rack.rb +3 -1
  29. data/lib/merb-core/rack/adapter.rb +7 -4
  30. data/lib/merb-core/rack/adapter/ebb.rb +12 -13
  31. data/lib/merb-core/rack/adapter/evented_mongrel.rb +2 -15
  32. data/lib/merb-core/rack/adapter/irb.rb +3 -2
  33. data/lib/merb-core/rack/adapter/mongrel.rb +22 -15
  34. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +4 -16
  35. data/lib/merb-core/rack/adapter/thin.rb +21 -22
  36. data/lib/merb-core/rack/adapter/thin_turbo.rb +4 -11
  37. data/lib/merb-core/rack/adapter/webrick.rb +54 -18
  38. data/lib/merb-core/rack/handler/mongrel.rb +12 -13
  39. data/lib/merb-core/rack/middleware/csrf.rb +1 -1
  40. data/lib/merb-core/server.rb +135 -98
  41. data/lib/merb-core/tasks/gem_management.rb +50 -12
  42. data/lib/merb-core/tasks/merb.rb +1 -0
  43. data/lib/merb-core/tasks/merb_rake_helper.rb +9 -38
  44. data/lib/merb-core/tasks/stats.rake +2 -2
  45. data/lib/merb-core/test.rb +9 -3
  46. data/lib/merb-core/test/helpers.rb +1 -0
  47. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -2
  48. data/lib/merb-core/test/helpers/request_helper.rb +40 -372
  49. data/lib/merb-core/test/helpers/route_helper.rb +15 -7
  50. data/lib/merb-core/test/matchers.rb +1 -0
  51. data/lib/merb-core/test/matchers/controller_matchers.rb +4 -247
  52. data/lib/merb-core/test/matchers/view_matchers.rb +22 -4
  53. data/lib/merb-core/test/run_specs.rb +117 -25
  54. data/lib/merb-core/version.rb +1 -1
  55. metadata +1 -1
  56. data/lib/merb-core/vendor/facets.rb +0 -2
  57. data/lib/merb-core/vendor/facets/dictionary.rb +0 -433
  58. data/lib/merb-core/vendor/facets/inflect.rb +0 -342
@@ -12,13 +12,21 @@ module Merb
12
12
  # ==== Returns
13
13
  # String:: The generated URL.
14
14
  def url(*args)
15
- name = args.first.is_a?(Symbol) ? args.shift : :default
16
-
17
- unless route = Merb::Router.named_routes[name]
18
- raise Merb::Router::GenerationError, "Named route not found: #{name}"
19
- end
20
-
21
- route.generate(args, @request_params || {})
15
+ args << (@request_params || {})
16
+ Merb::Router.url(*args)
17
+ end
18
+
19
+ # Mimics the resource method available to controllers
20
+ #
21
+ # ==== Paramaters
22
+ # resources<Object>:: The resources to generate URLs from
23
+ # params<Hash>:: Any extra parameters that are required.
24
+ #
25
+ # ==== Returns
26
+ # String:: The generated URL.
27
+ def resource(*args)
28
+ args << @request_params || {}
29
+ Merb::Router.resource(*args)
22
30
  end
23
31
 
24
32
  # ==== Parameters
@@ -3,6 +3,7 @@ module Merb::Test::Rspec; end
3
3
  require "merb-core/test/matchers/controller_matchers"
4
4
  require "merb-core/test/matchers/route_matchers"
5
5
  require "merb-core/test/matchers/view_matchers"
6
+ require "merb-core/test/matchers/request_matchers"
6
7
 
7
8
  Merb::Test::ControllerHelper.send(:include, Merb::Test::Rspec::ControllerMatchers)
8
9
  Merb::Test::RouteHelper.send(:include, Merb::Test::Rspec::RouteMatchers)
@@ -31,140 +31,6 @@ module Merb::Test::Rspec::ControllerMatchers
31
31
  end
32
32
  end
33
33
 
34
- class RedirectTo
35
-
36
- # === Parameters
37
- # String:: The expected location
38
- # Hash:: Optional hash of options (currently only :message)
39
- def initialize(expected, options = {})
40
- @expected = expected
41
- @options = options
42
- end
43
-
44
- # ==== Parameters
45
- # target<Merb::Controller>:: The controller to match
46
- #
47
- # ==== Returns
48
- # Boolean::
49
- # True if the controller status is redirect and the locations match.
50
- def matches?(target)
51
- @target, @location = target, target.headers['Location']
52
- @redirected = BeRedirect.new.matches?(target.status)
53
-
54
- if @options[:message]
55
- msg = Merb::Request.escape([Marshal.dump(@options[:message])].pack("m"))
56
- @expected << "?_message=#{msg}"
57
- end
58
-
59
- @location == @expected && @redirected
60
- end
61
-
62
- # ==== Returns
63
- # String:: The failure message.
64
- def failure_message
65
- msg = "expected #{inspect_target} to redirect to <#{@expected}>, but "
66
- if @redirected
67
- msg << "was <#{target_location}>"
68
- else
69
- msg << "there was no redirection"
70
- end
71
- end
72
-
73
- # ==== Returns
74
- # String:: The failure message to be displayed in negative matches.
75
- def negative_failure_message
76
- "expected #{inspect_target} not to redirect to <#{@expected}>, but did anyway"
77
- end
78
-
79
- # ==== Returns
80
- # String:: The controller and action name.
81
- def inspect_target
82
- "#{@target.controller_name}##{@target.action_name}"
83
- end
84
-
85
- # ==== Returns
86
- # String:: Either the target's location header or the target itself.
87
- def target_location
88
- @target.respond_to?(:headers) ? @target.headers['Location'] : @target
89
- end
90
- end
91
-
92
- class BeSuccess
93
-
94
- # ==== Parameters
95
- # target<Fixnum, ~status>::
96
- # Either the status code or a controller with a status code.
97
- #
98
- # ==== Returns
99
- # Boolean:: True if the status code is in the range 200..207.
100
- def matches?(target)
101
- @target = target
102
- (200..207).include?(status_code)
103
- end
104
-
105
- # ==== Returns
106
- # String:: The failure message.
107
- def failure_message
108
- "expected#{inspect_target} to be successful but was #{status_code}"
109
- end
110
-
111
- # ==== Returns
112
- # String:: The failure message to be displayed in negative matches.
113
- def negative_failure_message
114
- "expected#{inspect_target} not to be successful but it was #{status_code}"
115
- end
116
-
117
- # ==== Returns
118
- # String:: The controller and action name.
119
- def inspect_target
120
- " #{@target.controller_name}##{@target.action_name}" if @target.respond_to?(:controller_name) && @target.respond_to?(:action_name)
121
- end
122
-
123
- # ==== Returns
124
- # Fixnum:: Either the target's status or the target itself.
125
- def status_code
126
- @target.respond_to?(:status) ? @target.status : @target
127
- end
128
- end
129
-
130
- class BeMissing
131
-
132
- # ==== Parameters
133
- # target<Fixnum, ~status>::
134
- # Either the status code or a controller with a status code.
135
- #
136
- # ==== Returns
137
- # Boolean:: True if the status code is in the range 400..417.
138
- def matches?(target)
139
- @target = target
140
- (400..417).include?(status_code)
141
- end
142
-
143
- # ==== Returns
144
- # String:: The failure message.
145
- def failure_message
146
- "expected#{inspect_target} to be missing but was #{status_code}"
147
- end
148
-
149
- # ==== Returns
150
- # String:: The failure message to be displayed in negative matches.
151
- def negative_failure_message
152
- "expected#{inspect_target} not to be missing but it was #{status_code}"
153
- end
154
-
155
- # ==== Returns
156
- # String:: The controller and action name.
157
- def inspect_target
158
- " #{@target.controller_name}##{@target.action_name}" if @target.respond_to?(:controller_name) && @target.respond_to?(:action_name)
159
- end
160
-
161
- # ==== Returns
162
- # Fixnum:: Either the target's status or the target itself.
163
- def status_code
164
- @target.respond_to?(:status) ? @target.status : @target
165
- end
166
- end
167
-
168
34
  class BeError
169
35
  def initialize(expected)
170
36
  @expected = expected
@@ -185,6 +51,10 @@ module Merb::Test::Rspec::ControllerMatchers
185
51
  "expected #{@target} not to be a #{@expected} error, but it was"
186
52
  end
187
53
  end
54
+
55
+ def be_error(expected)
56
+ BeError.new(expected)
57
+ end
188
58
 
189
59
  class Provide
190
60
 
@@ -223,120 +93,7 @@ module Merb::Test::Rspec::ControllerMatchers
223
93
  @target.class_provided_formats
224
94
  end
225
95
  end
226
-
227
- # Passes if the target was redirected, or the target is a redirection (300
228
- # level) response code.
229
- #
230
- # ==== Examples
231
- # # Passes if the controller was redirected
232
- # controller.should redirect
233
- #
234
- # # Also works if the target is the response code
235
- # controller.status.should redirect
236
- #
237
- # ==== Notes
238
- # valid HTTP Redirection codes:
239
- # * 300: Multiple Choices
240
- # * 301: Moved Permanently
241
- # * 302: Moved Temporarily (HTTP/1.0)
242
- # * 302: Found (HTTP/1.1)
243
- # * 303: See Other (HTTP/1.1)
244
- # * 304: Not Modified
245
- # * 305: Use Proxy
246
- # * 307: Temporary Redirect
247
- #--
248
- # status codes based on: http://cheat.errtheblog.com/s/http_status_codes/
249
- def redirect
250
- BeRedirect.new
251
- end
252
-
253
- alias_method :be_redirection, :redirect
254
-
255
- # Passes if the target was redirected to the expected location.
256
- #
257
- # ==== Paramters
258
- # expected<String>:: A relative or absolute url.
259
- #
260
- # ==== Examples
261
- # # Passes if the controller was redirected to http://example.com/
262
- # controller.should redirect_to('http://example.com/')
263
- def redirect_to(expected, options = {})
264
- RedirectTo.new(expected, options)
265
- end
266
-
267
- alias_method :be_redirection_to, :redirect_to
268
-
269
- # Passes if the request that generated the target was successful, or the
270
- # target is a success (200 level) response code.
271
- #
272
- # ==== Examples
273
- # # Passes if the controller call was successful
274
- # controller.should respond_successfully
275
- #
276
- # # Also works if the target is the response code
277
- # controller.status.should respond_successfully
278
- #
279
- # ==== Notes
280
- # valid HTTP Success codes:
281
- # * 200: OK
282
- # * 201: Created
283
- # * 202: Accepted
284
- # * 203: Non-Authoritative Information
285
- # * 204: No Content
286
- # * 205: Reset Content
287
- # * 206: Partial Content
288
- # * 207: Multi-Status
289
- #--
290
- # status codes based on: http://cheat.errtheblog.com/s/http_status_codes/
291
- def respond_successfully
292
- BeSuccess.new
293
- end
294
-
295
- alias_method :be_successful, :respond_successfully
296
-
297
- # Passes if the request that generated the target was missing, or the target
298
- # is a client-side error (400 level) response code.
299
- #
300
- # ==== Examples
301
- # # Passes if the controller call was unknown or not understood
302
- # controller.should be_missing
303
- #
304
- # # Also passes if the target is a response code
305
- # controller.status.should be_missing
306
- #
307
- # ==== Notes
308
- # valid HTTP Client Error codes:
309
- # * 400: Bad Request
310
- # * 401: Unauthorized
311
- # * 402: Payment Required
312
- # * 403: Forbidden
313
- # * 404: Not Found
314
- # * 405: Method Not Allowed
315
- # * 406: Not Acceptable
316
- # * 407: Proxy Authentication Required
317
- # * 408: Request Timeout
318
- # * 409: Conflict
319
- # * 410: Gone
320
- # * 411: Length Required
321
- # * 412: Precondition Failed
322
- # * 413: Request Entity Too Large
323
- # * 414: Request-URI Too Long
324
- # * 415: Unsupported Media Type
325
- # * 416: Requested Range Not Satisfiable
326
- # * 417: Expectation Failed
327
- # * 422: Unprocessable Entity
328
- #--
329
- # status codes based on: http://cheat.errtheblog.com/s/http_status_codes/
330
- def be_missing
331
- BeMissing.new
332
- end
333
96
 
334
- def be_error(expected)
335
- BeError.new(expected)
336
- end
337
-
338
- alias_method :be_client_error, :be_missing
339
-
340
97
  # Passes if the controller actually provides the target format
341
98
  #
342
99
  # === Parameters
@@ -1,10 +1,26 @@
1
1
  module Merb::Test::Rspec::ViewMatchers
2
2
  class HaveXpath
3
- def initialize(expected)
4
- @expected = expected
3
+ def initialize(expected, type)
4
+ @expected, @type = expected, type
5
5
  end
6
6
 
7
7
  def matches?(stringlike)
8
+ send("matches_#{@type}?", stringlike)
9
+ end
10
+
11
+ def matches_rexml?(stringlike)
12
+ @document = case stringlike
13
+ when REXML::Document
14
+ stringlike.root
15
+ when REXML::Node
16
+ stringlike
17
+ when StringIO, String
18
+ REXML::Document.new(stringlike).root
19
+ end
20
+ !REXML::XPath.match(@document, @expected).empty?
21
+ end
22
+
23
+ def matches_libxml?(stringlike)
8
24
  @document = case stringlike
9
25
  when LibXML::XML::Document, LibXML::XML::Node
10
26
  stringlike
@@ -335,10 +351,12 @@ module Merb::Test::Rspec::ViewMatchers
335
351
  def have_xpath(expected)
336
352
  begin
337
353
  require "libxml"
354
+ type = "libxml"
338
355
  rescue LoadError => e
339
- puts "To use have_xpath helper you need to install libxml-ruby gem"
356
+ require "rexml/document"
357
+ type = "rexml"
340
358
  end
341
- HaveXpath.new(expected)
359
+ HaveXpath.new(expected, type)
342
360
  end
343
361
  alias_method :match_xpath, :have_xpath
344
362
 
@@ -1,6 +1,94 @@
1
1
  require 'rubygems'
2
- require 'open3'
3
2
  require 'benchmark'
3
+ require 'drb'
4
+ require 'spec'
5
+ require 'spec/runner/formatter/base_text_formatter'
6
+ require 'spec/spec_helper.rb'
7
+
8
+ # Load this stuff so it only has to be loaded once for the entire suite
9
+ require 'spec/mocks'
10
+ require 'spec/mocks/extensions'
11
+ require 'spec/runner/formatter/specdoc_formatter'
12
+ require 'base64'
13
+ require 'nkf'
14
+ require 'kconv'
15
+ require 'rack'
16
+
17
+ begin
18
+ require 'json'
19
+ rescue
20
+ require 'json/pure'
21
+ end
22
+
23
+ Merb::Dispatcher
24
+
25
+ module Spec
26
+ module Runner
27
+ module Formatter
28
+ class BaseTextFormatter
29
+ def dump_failure(counter, failure)
30
+ output = @options.error_stream
31
+ output.puts
32
+ output.puts "#{counter.to_s})"
33
+ output.puts colourise("#{failure.header}\n#{failure.exception.message}", failure)
34
+ output.puts format_backtrace(failure.exception.backtrace)
35
+ output.flush
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module Merb
43
+ class Counter
44
+ include DRb::DRbUndumped
45
+
46
+ attr_accessor :time
47
+ def initialize
48
+ @examples, @failures, @errors, @pending, @total_time = 0, 0, 0, 0, 0
49
+ @err = ""
50
+ @mutex = Mutex.new
51
+ end
52
+
53
+ def add(spec, out, err)
54
+ @mutex.synchronize do
55
+ puts
56
+ puts "Running #{spec}."
57
+ STDOUT.puts out
58
+ STDOUT.flush
59
+ match = out.match(/(\d+) examples?, (\d+) failures?(?:, (\d+) errors?)?(?:, (\d+) pending?)?/m)
60
+ time = out.match(/Finished in (\d+\.\d+) seconds/)
61
+ @total_time += time[1].to_f if time
62
+ if match
63
+ e, f, errors, pending = match[1..-1]
64
+ @examples += e.to_i
65
+ @failures += f.to_i
66
+ @errors += errors.to_i
67
+ @pending += pending.to_i
68
+ end
69
+ unless err.chomp.empty?
70
+ @err << err.chomp << "\n"
71
+ end
72
+ end
73
+ end
74
+
75
+ def report
76
+ puts @err
77
+ puts
78
+ if @failures != 0 || @errors != 0
79
+ print "\e[31m" # Red
80
+ elsif @pending != 0
81
+ print "\e[33m" # Yellow
82
+ else
83
+ print "\e[32m" # Green
84
+ end
85
+ puts "Total actual time: #{@total_time}"
86
+ puts "#{@examples} examples, #{@failures} failures, #{@errors} errors, #{@pending} pending, #{sprintf("suite run in %3.3f seconds", @time.real)}"
87
+ # TODO: we need to report pending examples all together
88
+ puts "\e[0m"
89
+ end
90
+ end
91
+ end
4
92
 
5
93
  # Runs specs in all files matching the file pattern.
6
94
  #
@@ -14,36 +102,40 @@ def run_specs(globs, spec_cmd='spec', run_opts = "-c", except = [])
14
102
  require "optparse"
15
103
  require "spec"
16
104
  globs = globs.is_a?(Array) ? globs : [globs]
17
- examples, failures, errors, pending = 0, 0, 0, 0
105
+
106
+ counter = Merb::Counter.new
107
+ forks = 0
18
108
 
19
109
  time = Benchmark.measure do
110
+ pid = nil
20
111
  globs.each do |glob|
21
- (Dir[glob] - except).each do |spec|
22
- STDOUT.puts "\n\nRunning #{spec}...\n"
23
- response = Open3.popen3("#{spec_cmd} #{File.expand_path(spec)} #{run_opts}") do |i,o,e|
24
- while out = o.gets
25
- STDOUT.puts out
26
- #STDOUT.flush
27
- if out =~ /\d+ example/
28
- e, f, p = out.match(/(\d+) examples?, (\d+) failures?(?:, (\d+) pending?)?/)[1..-1]
29
- examples += e.to_i; failures += f.to_i; pending += p.to_i
30
- end
112
+ Dir[glob].each do |spec|
113
+ drb_uri = DRb.start_service(nil, counter).uri
114
+ Kernel.fork do
115
+ $VERBOSE = nil
116
+ DRb.stop_service
117
+ DRb.start_service
118
+ counter_client = DRbObject.new_with_uri(drb_uri)
119
+ err, out = StringIO.new, StringIO.new
120
+ def out.tty?() true end
121
+ options = Spec::Runner::OptionParser.parse(%W(#{spec} -fs --color), err, out)
122
+ options.filename_pattern = File.expand_path(spec)
123
+ Spec::Runner::CommandLine.run(options)
124
+ begin
125
+ counter_client.add(spec, out.string, err.string)
126
+ rescue DRb::DRbConnError => e
127
+ puts "#{Process.pid}: Exception caught"
128
+ puts "#{e.class}: #{e.message}"
129
+ retry
31
130
  end
32
- errors += 1 if e.is_a?(IO)
33
- STDOUT.puts e.read if e.is_a?(IO)
131
+ exit
34
132
  end
35
133
  end
134
+ Process.waitall
36
135
  end
37
136
  end
38
-
39
- puts
40
- puts "*** TOTALS ***"
41
- if failures == 0
42
- print "\e[32m"
43
- else
44
- print "\e[31m"
45
- end
46
- puts "#{examples} examples, #{failures} failures, #{errors} errors, #{pending} pending, #{sprintf("suite run in %3.3f seconds", time.real)}"
47
- # TODO: we need to report pending examples all together
48
- print "\e[0m"
137
+
138
+ counter.time = time
139
+ counter.report
140
+ exit!
49
141
  end