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



162
163
164
165
166
167
# File 'lib/motion-spec/specification.rb', line 162

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

#execute_blockObject



176
177
178
179
180
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
# File 'lib/motion-spec/specification.rb', line 176

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 { |line, i|
      ErrorLog << "\t#{line}#{i == 0 ? ": #{@context.name} - #{@description}" : ''}\n"
    }
    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



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

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

#finish_specObject



153
154
155
156
157
158
159
160
# File 'lib/motion-spec/specification.rb', line 153

def finish_spec
  if !@exception_occurred && Counter[:requirements] == @number_of_requirements_before
    # the specification did not contain any requirements, so it flunked
    execute_block { raise 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



104
105
106
# File 'lib/motion-spec/specification.rb', line 104

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

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



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

def postpone_block(timeout = 1, &block)
  # If an exception occurred, we definitely don't need to schedule any more blocks
  return if @exception_occurred
  raise 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



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

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
  raise 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



121
122
123
124
125
126
# File 'lib/motion-spec/specification.rb', line 121

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

#postponed_change_block_timeout_exceededObject



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

def postponed_change_block_timeout_exceeded
  remove_observer!
  postponed_block_timeout_exceeded
end

#remove_observer!Object



113
114
115
116
117
118
119
# File 'lib/motion-spec/specification.rb', line 113

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



128
129
130
131
132
133
134
135
136
# File 'lib/motion-spec/specification.rb', line 128

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



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

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
# 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) } }
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



138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/motion-spec/specification.rb', line 138

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



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

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