Class: MotionSpec::Specification

Inherits:
Object
  • Object
show all
Defined in:
lib/motion-spec/specification.rb

Constant Summary collapse

MULTIPLE_POSTPONES_ERROR_MESSAGE =
"Only one indefinite `wait' block at the same time is allowed!"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, description, block, before_filters, after_filters) ⇒ Specification

Returns a new instance of Specification.



9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/motion-spec/specification.rb', line 9

def initialize(context, description, block, before_filters, after_filters)
  @context = context
  @description = description
  @block = block
  @before_filters = before_filters.dup
  @after_filters = after_filters.dup

  @postponed_blocks_count = 0
  @ran_spec_block = false
  @ran_after_filters = false
  @exception_occurred = false
  @error = ''
end

Instance Attribute Details

#descriptionObject (readonly)

Returns the value of attribute description.



7
8
9
# File 'lib/motion-spec/specification.rb', line 7

def description
  @description
end

Instance Method Details

#cancel_scheduled_requests!Object



167
168
169
170
171
172
# File 'lib/motion-spec/specification.rb', line 167

def cancel_scheduled_requests!
  unless Platform.android?
    NSObject.cancelPreviousPerformRequestsWithTarget(@context)
    NSObject.cancelPreviousPerformRequestsWithTarget(self)
  end
end

#execute_blockObject



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/motion-spec/specification.rb', line 181

def execute_block
  yield
rescue Object => e
  @exception_occurred = true

  if e.is_a?(Exception)
    ErrorLog << "#{e.class}: #{e.message}\n"
    lines = $DEBUG ? e.backtrace : e.backtrace.find_all { |line| line !~ /bin\/macbacon|\/mac_bacon\.rb:\d+/ }
    lines.each_with_index do |line, i|
      ErrorLog << "\t#{line}#{i == 0 ? ": #{@context.name} - #{@description}" : ''}\n"
    end
    ErrorLog << "\n"
  else
    if defined?(NSException)
      # Pure NSException.
      ErrorLog << "#{e.name}: #{e.reason}\n"
    else
      # Pure Java exception.
      ErrorLog << "#{e.class.toString} : #{e.getMessage}"
    end
  end

  @error =
    if e.is_a? Error
      Counter[e.count_as] += 1
      "#{e.count_as.to_s.upcase} - #{e}"
    else
      Counter[:errors] += 1
      "ERROR: #{e.class} - #{e}"
    end
end

#exit_specObject



174
175
176
177
178
179
# File 'lib/motion-spec/specification.rb', line 174

def exit_spec
  cancel_scheduled_requests!
  Counter[:depth] -= 1
  MotionSpec.handle_requirement_end(@error)
  @context.specification_did_finish(self)
end

#finish_specObject



158
159
160
161
162
163
164
165
# File 'lib/motion-spec/specification.rb', line 158

def finish_spec
  if !@exception_occurred && Counter[:requirements] == @number_of_requirements_before
    # the specification did not contain any requirements, so it flunked
    execute_block { fail Error.new(:missing, "empty specification: #{@context.name} #{@description}") }
  end
  run_after_filters
  exit_spec unless postponed?
end

#observeValueForKeyPath(_key_path, ofObject: object, change: _, context: __) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



107
108
109
# File 'lib/motion-spec/specification.rb', line 107

def observeValueForKeyPath(_key_path, ofObject:object, change:_, context:__)
  resume
end

#postpone_block(timeout = 1, &block) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/motion-spec/specification.rb', line 68

def postpone_block(timeout = 1, &block)
  # If an exception occurred, we definitely don't need to schedule any more blocks
  return if @exception_occurred
  fail MULTIPLE_POSTPONES_ERROR_MESSAGE if @postponed_block

  @postponed_blocks_count += 1
  @postponed_block = block

  return performSelector(
    'postponed_block_timeout_exceeded',
    withObject: nil,
    afterDelay: timeout
  ) unless Platform.android?

  sleep timeout
  postponed_block_timeout_exceeded
end

#postpone_block_until_change(object_to_observe, key_path, timeout = 1, &block) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/motion-spec/specification.rb', line 86

def postpone_block_until_change(object_to_observe, key_path, timeout = 1, &block)
  # If an exception occurred, we definitely don't need to schedule any more blocks
  return if @exception_occurred
  fail MULTIPLE_POSTPONES_ERROR_MESSAGE if @postponed_block

  @postponed_blocks_count += 1
  @postponed_block = block
  @observed_object_and_key_path = [object_to_observe, key_path]
  object_to_observe.addObserver(self, forKeyPath: key_path, options: 0, context: nil)

  return performSelector(
    'postponed_change_block_timeout_exceeded',
    withObject: nil,
    afterDelay: timeout
  ) unless Platform.android?

  sleep timeout
  postponed_change_block_timeout_exceeded
end

#postponed?Boolean

Returns:

  • (Boolean)


23
24
25
# File 'lib/motion-spec/specification.rb', line 23

def postponed?
  @postponed_blocks_count != 0
end

#postponed_block_timeout_exceededObject



125
126
127
128
129
130
# File 'lib/motion-spec/specification.rb', line 125

def postponed_block_timeout_exceeded
  cancel_scheduled_requests!
  execute_block { fail Error.new(:failed, "timeout exceeded: #{@context.name} - #{@description}") }
  @postponed_blocks_count = 0
  finish_spec
end

#postponed_change_block_timeout_exceededObject

rubocop:enable Lint/UnusedMethodArgument



112
113
114
115
# File 'lib/motion-spec/specification.rb', line 112

def postponed_change_block_timeout_exceeded
  remove_observer!
  postponed_block_timeout_exceeded
end

#remove_observer!Object



117
118
119
120
121
122
123
# File 'lib/motion-spec/specification.rb', line 117

def remove_observer!
  if @observed_object_and_key_path
    object, key_path = @observed_object_and_key_path
    object.removeObserver(self, forKeyPath: key_path)
    @observed_object_and_key_path = nil
  end
end

#resumeObject



132
133
134
135
136
137
138
139
140
141
# File 'lib/motion-spec/specification.rb', line 132

def resume
  unless Platform.android?
    NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: 'postponed_block_timeout_exceeded', object: nil)
    NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: 'postponed_change_block_timeout_exceeded', object: nil)
  end
  remove_observer!
  block = @postponed_block
  @postponed_block = nil
  run_postponed_block(block)
end

#runObject



47
48
49
50
51
52
53
# File 'lib/motion-spec/specification.rb', line 47

def run
  MotionSpec.handle_requirement_begin(@description)
  Counter[:depth] += 1
  run_before_filters
  @number_of_requirements_before = Counter[:requirements]
  run_spec_block unless postponed?
end

#run_after_filtersObject



40
41
42
43
44
45
# File 'lib/motion-spec/specification.rb', line 40

def run_after_filters
  @ran_after_filters = true
  execute_block { @after_filters.each { |f| @context.instance_eval(&f) } }
  Mocks.clear!
  Stubs.clear!
end

#run_before_filtersObject



27
28
29
# File 'lib/motion-spec/specification.rb', line 27

def run_before_filters
  execute_block { @before_filters.each { |f| @context.instance_eval(&f) } }
end

#run_postponed_block(block) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/motion-spec/specification.rb', line 143

def run_postponed_block(block)
  # If an exception occurred, we definitely don't need execute any more blocks
  execute_block(&block) unless @exception_occurred
  @postponed_blocks_count -= 1
  unless postponed?
    if @ran_after_filters
      exit_spec
    elsif @ran_spec_block
      finish_spec
    else
      run_spec_block
    end
  end
end

#run_spec_blockObject



31
32
33
34
35
36
37
38
# File 'lib/motion-spec/specification.rb', line 31

def run_spec_block
  @ran_spec_block = true
  # If an exception occurred, we definitely don't need to perform the actual spec anymore
  unless @exception_occurred
    execute_block { @context.instance_eval(&@block) }
  end
  finish_spec unless postponed?
end

#schedule_block(seconds, &block) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/motion-spec/specification.rb', line 55

def schedule_block(seconds, &block)
  # If an exception occurred, we definitely don't need to schedule any more blocks
  return if @exception_occurred

  @postponed_blocks_count += 1
  if Platform.android?
    sleep seconds
    run_postponed_block(block)
  else
    performSelector('run_postponed_block:', withObject: block, afterDelay: seconds)
  end
end