/vendor/bundle/jruby/2.1/gems/rspec-core-2.14.8/lib/rspec/core/memoized_helpers.rb

https://github.com/delowong/logstash · Ruby · 514 lines · 152 code · 30 blank · 332 comment · 11 complexity · 2b651e67d9d969c9c64bdabcef5e5f97 MD5 · raw file

  1. module RSpec
  2. module Core
  3. module MemoizedHelpers
  4. # @note `subject` was contributed by Joe Ferris to support the one-liner
  5. # syntax embraced by shoulda matchers:
  6. #
  7. # describe Widget do
  8. # it { should validate_presence_of(:name) }
  9. # end
  10. #
  11. # While the examples below demonstrate how to use `subject`
  12. # explicitly in examples, we recommend that you define a method with
  13. # an intention revealing name instead.
  14. #
  15. # @example
  16. #
  17. # # explicit declaration of subject
  18. # describe Person do
  19. # subject { Person.new(:birthdate => 19.years.ago) }
  20. # it "should be eligible to vote" do
  21. # subject.should be_eligible_to_vote
  22. # # ^ ^ explicit reference to subject not recommended
  23. # end
  24. # end
  25. #
  26. # # implicit subject => { Person.new }
  27. # describe Person do
  28. # it "should be eligible to vote" do
  29. # subject.should be_eligible_to_vote
  30. # # ^ ^ explicit reference to subject not recommended
  31. # end
  32. # end
  33. #
  34. # # one-liner syntax - should is invoked on subject
  35. # describe Person do
  36. # it { should be_eligible_to_vote }
  37. # end
  38. #
  39. # @note Because `subject` is designed to create state that is reset between
  40. # each example, and `before(:all)` is designed to setup state that is
  41. # shared across _all_ examples in an example group, `subject` is _not_
  42. # intended to be used in a `before(:all)` hook. RSpec 2.13.1 prints
  43. # a warning when you reference a `subject` from `before(:all)` and we plan
  44. # to have it raise an error in RSpec 3.
  45. #
  46. # @see #should
  47. def subject
  48. __memoized.fetch(:subject) do
  49. __memoized[:subject] = begin
  50. described = described_class || self.class.description
  51. Class === described ? described.new : described
  52. end
  53. end
  54. end
  55. # When `should` is called with no explicit receiver, the call is
  56. # delegated to the object returned by `subject`. Combined with an
  57. # implicit subject this supports very concise expressions.
  58. #
  59. # @example
  60. #
  61. # describe Person do
  62. # it { should be_eligible_to_vote }
  63. # end
  64. #
  65. # @see #subject
  66. def should(matcher=nil, message=nil)
  67. RSpec::Expectations::PositiveExpectationHandler.handle_matcher(subject, matcher, message)
  68. end
  69. # Just like `should`, `should_not` delegates to the subject (implicit or
  70. # explicit) of the example group.
  71. #
  72. # @example
  73. #
  74. # describe Person do
  75. # it { should_not be_eligible_to_vote }
  76. # end
  77. #
  78. # @see #subject
  79. def should_not(matcher=nil, message=nil)
  80. RSpec::Expectations::NegativeExpectationHandler.handle_matcher(subject, matcher, message)
  81. end
  82. private
  83. # @private
  84. def __memoized
  85. @__memoized ||= {}
  86. end
  87. # Used internally to customize the behavior of the
  88. # memoized hash when used in a `before(:all)` hook.
  89. #
  90. # @private
  91. class BeforeAllMemoizedHash
  92. def initialize(example_group_instance)
  93. @example_group_instance = example_group_instance
  94. @hash = {}
  95. end
  96. def self.isolate_for_before_all(example_group_instance)
  97. example_group_instance.instance_eval do
  98. @__memoized = BeforeAllMemoizedHash.new(self)
  99. begin
  100. yield
  101. ensure
  102. @__memoized.preserve_accessed_lets
  103. @__memoized = nil
  104. end
  105. end
  106. end
  107. def fetch(key, &block)
  108. description = if key == :subject
  109. "subject"
  110. else
  111. "let declaration `#{key}`"
  112. end
  113. ::RSpec.warn_deprecation <<-EOS
  114. WARNING: #{description} accessed in a `before(:all)` hook at:
  115. #{caller[1]}
  116. This is deprecated behavior that will not be supported in RSpec 3.
  117. `let` and `subject` declarations are not intended to be called
  118. in a `before(:all)` hook, as they exist to define state that
  119. is reset between each example, while `before(:all)` exists to
  120. define state that is shared across examples in an example group.
  121. EOS
  122. @hash.fetch(key, &block)
  123. end
  124. def []=(key, value)
  125. @hash[key] = value
  126. end
  127. def preserve_accessed_lets
  128. hash = @hash
  129. @example_group_instance.class.class_eval do
  130. hash.each do |key, value|
  131. undef_method(key) if method_defined?(key)
  132. define_method(key) { value }
  133. end
  134. end
  135. end
  136. end
  137. def self.included(mod)
  138. mod.extend(ClassMethods)
  139. end
  140. module ClassMethods
  141. # Generates a method whose return value is memoized after the first
  142. # call. Useful for reducing duplication between examples that assign
  143. # values to the same local variable.
  144. #
  145. # @note `let` _can_ enhance readability when used sparingly (1,2, or
  146. # maybe 3 declarations) in any given example group, but that can
  147. # quickly degrade with overuse. YMMV.
  148. #
  149. # @note `let` uses an `||=` conditional that has the potential to
  150. # behave in surprising ways in examples that spawn separate threads,
  151. # though we have yet to see this in practice. You've been warned.
  152. #
  153. # @note Because `let` is designed to create state that is reset between
  154. # each example, and `before(:all)` is designed to setup state that is
  155. # shared across _all_ examples in an example group, `let` is _not_
  156. # intended to be used in a `before(:all)` hook. RSpec 2.13.1 prints
  157. # a warning when you reference a `let` from `before(:all)` and we plan
  158. # to have it raise an error in RSpec 3.
  159. #
  160. # @example
  161. #
  162. # describe Thing do
  163. # let(:thing) { Thing.new }
  164. #
  165. # it "does something" do
  166. # # first invocation, executes block, memoizes and returns result
  167. # thing.do_something
  168. #
  169. # # second invocation, returns the memoized value
  170. # thing.should be_something
  171. # end
  172. # end
  173. def let(name, &block)
  174. # We have to pass the block directly to `define_method` to
  175. # allow it to use method constructs like `super` and `return`.
  176. raise "#let or #subject called without a block" if block.nil?
  177. MemoizedHelpers.module_for(self).send(:define_method, name, &block)
  178. # Apply the memoization. The method has been defined in an ancestor
  179. # module so we can use `super` here to get the value.
  180. define_method(name) do
  181. __memoized.fetch(name) { |k| __memoized[k] = super(&nil) }
  182. end
  183. end
  184. # Just like `let`, except the block is invoked by an implicit `before`
  185. # hook. This serves a dual purpose of setting up state and providing a
  186. # memoized reference to that state.
  187. #
  188. # @example
  189. #
  190. # class Thing
  191. # def self.count
  192. # @count ||= 0
  193. # end
  194. #
  195. # def self.count=(val)
  196. # @count += val
  197. # end
  198. #
  199. # def self.reset_count
  200. # @count = 0
  201. # end
  202. #
  203. # def initialize
  204. # self.class.count += 1
  205. # end
  206. # end
  207. #
  208. # describe Thing do
  209. # after(:each) { Thing.reset_count }
  210. #
  211. # context "using let" do
  212. # let(:thing) { Thing.new }
  213. #
  214. # it "is not invoked implicitly" do
  215. # Thing.count.should eq(0)
  216. # end
  217. #
  218. # it "can be invoked explicitly" do
  219. # thing
  220. # Thing.count.should eq(1)
  221. # end
  222. # end
  223. #
  224. # context "using let!" do
  225. # let!(:thing) { Thing.new }
  226. #
  227. # it "is invoked implicitly" do
  228. # Thing.count.should eq(1)
  229. # end
  230. #
  231. # it "returns memoized version on first invocation" do
  232. # thing
  233. # Thing.count.should eq(1)
  234. # end
  235. # end
  236. # end
  237. def let!(name, &block)
  238. let(name, &block)
  239. before { __send__(name) }
  240. end
  241. # Declares a `subject` for an example group which can then be the
  242. # implicit receiver (through delegation) of calls to `should`.
  243. #
  244. # Given a `name`, defines a method with that name which returns the
  245. # `subject`. This lets you declare the subject once and access it
  246. # implicitly in one-liners and explicitly using an intention revealing
  247. # name.
  248. #
  249. # @param [String,Symbol] name used to define an accessor with an
  250. # intention revealing name
  251. # @param block defines the value to be returned by `subject` in examples
  252. #
  253. # @example
  254. #
  255. # describe CheckingAccount, "with $50" do
  256. # subject { CheckingAccount.new(Money.new(50, :USD)) }
  257. # it { should have_a_balance_of(Money.new(50, :USD)) }
  258. # it { should_not be_overdrawn }
  259. # end
  260. #
  261. # describe CheckingAccount, "with a non-zero starting balance" do
  262. # subject(:account) { CheckingAccount.new(Money.new(50, :USD)) }
  263. # it { should_not be_overdrawn }
  264. # it "has a balance equal to the starting balance" do
  265. # account.balance.should eq(Money.new(50, :USD))
  266. # end
  267. # end
  268. #
  269. # @see MemoizedHelpers#should
  270. def subject(name=nil, &block)
  271. if name
  272. let(name, &block)
  273. alias_method :subject, name
  274. self::NamedSubjectPreventSuper.send(:define_method, name) do
  275. raise NotImplementedError, "`super` in named subjects is not supported"
  276. end
  277. else
  278. let(:subject, &block)
  279. end
  280. end
  281. # Just like `subject`, except the block is invoked by an implicit `before`
  282. # hook. This serves a dual purpose of setting up state and providing a
  283. # memoized reference to that state.
  284. #
  285. # @example
  286. #
  287. # class Thing
  288. # def self.count
  289. # @count ||= 0
  290. # end
  291. #
  292. # def self.count=(val)
  293. # @count += val
  294. # end
  295. #
  296. # def self.reset_count
  297. # @count = 0
  298. # end
  299. #
  300. # def initialize
  301. # self.class.count += 1
  302. # end
  303. # end
  304. #
  305. # describe Thing do
  306. # after(:each) { Thing.reset_count }
  307. #
  308. # context "using subject" do
  309. # subject { Thing.new }
  310. #
  311. # it "is not invoked implicitly" do
  312. # Thing.count.should eq(0)
  313. # end
  314. #
  315. # it "can be invoked explicitly" do
  316. # subject
  317. # Thing.count.should eq(1)
  318. # end
  319. # end
  320. #
  321. # context "using subject!" do
  322. # subject!(:thing) { Thing.new }
  323. #
  324. # it "is invoked implicitly" do
  325. # Thing.count.should eq(1)
  326. # end
  327. #
  328. # it "returns memoized version on first invocation" do
  329. # subject
  330. # Thing.count.should eq(1)
  331. # end
  332. # end
  333. # end
  334. def subject!(name=nil, &block)
  335. subject(name, &block)
  336. before { subject }
  337. end
  338. # Creates a nested example group named by the submitted `attribute`,
  339. # and then generates an example using the submitted block.
  340. #
  341. # @example
  342. #
  343. # # This ...
  344. # describe Array do
  345. # its(:size) { should eq(0) }
  346. # end
  347. #
  348. # # ... generates the same runtime structure as this:
  349. # describe Array do
  350. # describe "size" do
  351. # it "should eq(0)" do
  352. # subject.size.should eq(0)
  353. # end
  354. # end
  355. # end
  356. #
  357. # The attribute can be a `Symbol` or a `String`. Given a `String`
  358. # with dots, the result is as though you concatenated that `String`
  359. # onto the subject in an expression.
  360. #
  361. # @example
  362. #
  363. # describe Person do
  364. # subject do
  365. # Person.new.tap do |person|
  366. # person.phone_numbers << "555-1212"
  367. # end
  368. # end
  369. #
  370. # its("phone_numbers.first") { should eq("555-1212") }
  371. # end
  372. #
  373. # When the subject is a `Hash`, you can refer to the Hash keys by
  374. # specifying a `Symbol` or `String` in an array.
  375. #
  376. # @example
  377. #
  378. # describe "a configuration Hash" do
  379. # subject do
  380. # { :max_users => 3,
  381. # 'admin' => :all_permissions }
  382. # end
  383. #
  384. # its([:max_users]) { should eq(3) }
  385. # its(['admin']) { should eq(:all_permissions) }
  386. #
  387. # # You can still access to its regular methods this way:
  388. # its(:keys) { should include(:max_users) }
  389. # its(:count) { should eq(2) }
  390. # end
  391. #
  392. # Note that this method does not modify `subject` in any way, so if you
  393. # refer to `subject` in `let` or `before` blocks, you're still
  394. # referring to the outer subject.
  395. #
  396. # @example
  397. #
  398. # describe Person do
  399. # subject { Person.new }
  400. # before { subject.age = 25 }
  401. # its(:age) { should eq(25) }
  402. # end
  403. def its(attribute, &block)
  404. describe(attribute) do
  405. if Array === attribute
  406. let(:__its_subject) { subject[*attribute] }
  407. else
  408. let(:__its_subject) do
  409. attribute_chain = attribute.to_s.split('.')
  410. attribute_chain.inject(subject) do |inner_subject, attr|
  411. inner_subject.send(attr)
  412. end
  413. end
  414. end
  415. def should(matcher=nil, message=nil)
  416. RSpec::Expectations::PositiveExpectationHandler.handle_matcher(__its_subject, matcher, message)
  417. end
  418. def should_not(matcher=nil, message=nil)
  419. RSpec::Expectations::NegativeExpectationHandler.handle_matcher(__its_subject, matcher, message)
  420. end
  421. example(&block)
  422. end
  423. end
  424. end
  425. # @api private
  426. #
  427. # Gets the LetDefinitions module. The module is mixed into
  428. # the example group and is used to hold all let definitions.
  429. # This is done so that the block passed to `let` can be
  430. # forwarded directly on to `define_method`, so that all method
  431. # constructs (including `super` and `return`) can be used in
  432. # a `let` block.
  433. #
  434. # The memoization is provided by a method definition on the
  435. # example group that supers to the LetDefinitions definition
  436. # in order to get the value to memoize.
  437. def self.module_for(example_group)
  438. get_constant_or_yield(example_group, :LetDefinitions) do
  439. mod = Module.new do
  440. include Module.new {
  441. example_group.const_set(:NamedSubjectPreventSuper, self)
  442. }
  443. end
  444. example_group.const_set(:LetDefinitions, mod)
  445. mod
  446. end
  447. end
  448. # @api private
  449. def self.define_helpers_on(example_group)
  450. example_group.send(:include, module_for(example_group))
  451. end
  452. if Module.method(:const_defined?).arity == 1 # for 1.8
  453. # @api private
  454. #
  455. # Gets the named constant or yields.
  456. # On 1.8, const_defined? / const_get do not take into
  457. # account the inheritance hierarchy.
  458. def self.get_constant_or_yield(example_group, name)
  459. if example_group.const_defined?(name)
  460. example_group.const_get(name)
  461. else
  462. yield
  463. end
  464. end
  465. else
  466. # @api private
  467. #
  468. # Gets the named constant or yields.
  469. # On 1.9, const_defined? / const_get take into account the
  470. # the inheritance by default, and accept an argument to
  471. # disable this behavior. It's important that we don't
  472. # consider inheritance here; each example group level that
  473. # uses a `let` should get its own `LetDefinitions` module.
  474. def self.get_constant_or_yield(example_group, name)
  475. if example_group.const_defined?(name, (check_ancestors = false))
  476. example_group.const_get(name, check_ancestors)
  477. else
  478. yield
  479. end
  480. end
  481. end
  482. end
  483. end
  484. end