dynflow 1.4.7 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffff17987553c70b701f8f7e8b98f5a159c4d0498ccb45b9d41581204edb1b01
4
- data.tar.gz: 1399c875ec759cf98a53925caa822e7e03aa8e1e61d9bd2e1957ab69bb07beb8
3
+ metadata.gz: 5b33bcad7864102ad9f0f3bb654c7990b3037b7b0620b770d5f34f1495402855
4
+ data.tar.gz: f4089f0cb793384a764f4aa042268c19d174c989c5d348f63e126eac5fb94716
5
5
  SHA512:
6
- metadata.gz: 51f14bfdcae3d32aaae92716a48668f119327cb8873130dbff7ce9551b40f64d0283037c5a77c5fdced78b612ed8046a449c44ec55e1377d56d247a0cedf3256
7
- data.tar.gz: 54787c9fe54caa7227c4122470e398891e161618f5bb02f49332ea2e5a54190dd263e3d46a12691fe47218a81d026d67417b501e07785d6e9eda0aa89f4dd916
6
+ metadata.gz: c119f0ce0d3605012206b083b829bc85194d676f762d61a394be6257d376c56388efd72f4a77f78445ece78942c5eb561728031e9a703767113eab7780b9d0e3
7
+ data.tar.gz: 136dafb81d0766e9c1041a9a52896b4cbe0d300482be729900fd9fe8f83e6095bbc084a6d43d0aa9012b436544690d3c2d774d46f0b9c573775b3707daec3428
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
+ set -x
4
+
3
5
  echo "Setting the environment to use ${DB} database"
4
6
 
5
7
  BUNDLE_CONFIG=.bundle/config
@@ -12,11 +14,9 @@ EOF
12
14
  case $DB in
13
15
  mysql)
14
16
  sed -i 's/:mysql//'g $BUNDLE_CONFIG
15
- mysql -e 'create database travis_ci_test;'
16
17
  ;;
17
18
  postgresql)
18
19
  sed -i 's/:postgresql//'g $BUNDLE_CONFIG
19
- psql -c 'create database travis_ci_test;' -U postgres
20
20
  ;;
21
21
  sqlite3)
22
22
  # the tests are by default using sqlite3: do nothing
@@ -0,0 +1,116 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on: [pull_request]
11
+
12
+ env:
13
+ TESTOPTS: --verbose
14
+
15
+ jobs:
16
+ rubocop:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - name: Setup Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: 2.7
24
+ - name: Setup
25
+ run: |
26
+ gem install bundler
27
+ bundle install --jobs=3 --retry=3
28
+ - name: Run rubocop
29
+ run: bundle exec rubocop
30
+
31
+ test:
32
+ runs-on: ubuntu-latest
33
+ needs: rubocop
34
+ strategy:
35
+ fail-fast: false
36
+ matrix:
37
+ ruby_version:
38
+ - 2.5.0
39
+ - 2.6.0
40
+ - 2.7.0
41
+ - 3.0.0
42
+ concurrent_ruby_ext:
43
+ - 'true'
44
+ - 'false'
45
+ db:
46
+ - postgresql
47
+ - mysql
48
+ - sqlite3
49
+ include:
50
+ - db: postgresql
51
+ conn_string: postgres://postgres@localhost/travis_ci_test
52
+ - db: mysql
53
+ conn_string: mysql2://root@127.0.0.1/travis_ci_test
54
+ - db: sqlite3
55
+ conn_string: sqlite:/
56
+ exclude:
57
+ - db: mysql
58
+ ruby_version: 2.5.0
59
+ - db: mysql
60
+ ruby_version: 2.6.0
61
+ - db: mysql
62
+ ruby_version: 3.0.0
63
+ - db: mysql
64
+ concurrent_ruby_ext: 'true'
65
+ - db: sqlite3
66
+ ruby_version: 2.5.0
67
+ - db: sqlite3
68
+ ruby_version: 2.6.0
69
+ - db: sqlite3
70
+ ruby_version: 3.0.0
71
+ - db: sqlite3
72
+ concurrent_ruby_ext: 'true'
73
+ - db: postgresql
74
+ ruby_version: 2.5.0
75
+ concurrent_ruby_ext: 'true'
76
+ - db: postgresql
77
+ ruby_version: 2.6.0
78
+ concurrent_ruby_ext: 'true'
79
+ - db: postgresql
80
+ ruby_version: 3.0.0
81
+ concurrent_ruby_ext: 'true'
82
+
83
+ services:
84
+ postgres:
85
+ image: postgres:12.1
86
+ ports: ['5432:5432']
87
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
88
+ env:
89
+ POSTGRES_DB: travis_ci_test
90
+ mariadb:
91
+ image: mariadb:10
92
+ ports: ['3306:3306']
93
+ env:
94
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
95
+ MYSQL_DATABASE: travis_ci_test
96
+ redis:
97
+ image: redis:latest
98
+ ports: ['6379:6379']
99
+
100
+ env:
101
+ DB: ${{ matrix.db }}
102
+ DB_CONN_STRING: ${{ matrix.conn_string }}
103
+ CONCURRENT_RUBY_EXT: "${{ matrix.concurrent_ruby_ext }}"
104
+
105
+ steps:
106
+ - uses: actions/checkout@v2
107
+ - name: Set up Ruby
108
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
109
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
110
+ uses: ruby/setup-ruby@v1
111
+ with:
112
+ ruby-version: ${{ matrix.ruby_version }}
113
+ - name: Install dependencies
114
+ run: .github/install_dependencies.sh
115
+ - name: Run tests
116
+ run: bundle exec rake test
data/lib/dynflow.rb CHANGED
@@ -76,7 +76,7 @@ module Dynflow
76
76
  if defined? ::ActiveJob
77
77
  require 'dynflow/active_job/queue_adapter'
78
78
 
79
- class Railtie < Rails::Railtie
79
+ class Railtie < ::Rails::Railtie
80
80
  config.before_initialize do
81
81
  ::ActiveJob::QueueAdapters.send(
82
82
  :include,
@@ -93,7 +93,8 @@ module Dynflow
93
93
  fields! execution_plan_id: String,
94
94
  step_id: Integer,
95
95
  event: Object,
96
- time: type { variants Time, NilClass }
96
+ time: type { variants Time, NilClass },
97
+ optional: Algebrick::Types::Boolean
97
98
  end
98
99
 
99
100
  def self.constantize(action_name)
@@ -332,9 +333,9 @@ module Dynflow
332
333
 
333
334
  # Plan an +event+ to be send to the action defined by +action+, what defaults to be self.
334
335
  # if +time+ is not passed, event is sent as soon as possible.
335
- def plan_event(event, time = nil, execution_plan_id: self.execution_plan_id, step_id: self.run_step_id)
336
+ def plan_event(event, time = nil, execution_plan_id: self.execution_plan_id, step_id: self.run_step_id, optional: false)
336
337
  time = @world.clock.current_time + time if time.is_a?(Numeric)
337
- delayed_events << DelayedEvent[execution_plan_id, step_id, event, time]
338
+ delayed_events << DelayedEvent[execution_plan_id, step_id, event, time, optional]
338
339
  end
339
340
 
340
341
  def delayed_events
@@ -9,14 +9,14 @@ module Dynflow
9
9
  @step_id = action.run_step_id
10
10
  end
11
11
 
12
- def plan_event(event, time, sent = Concurrent::Promises.resolvable_future)
13
- @world.plan_event(execution_plan_id, step_id, event, time, sent)
12
+ def plan_event(event, time, sent = Concurrent::Promises.resolvable_future, optional: false)
13
+ @world.plan_event(execution_plan_id, step_id, event, time, sent, optional: optional)
14
14
  end
15
15
 
16
- def event(event, sent = Concurrent::Promises.resolvable_future)
16
+ def event(event, sent = Concurrent::Promises.resolvable_future, optional: false)
17
17
  # TODO: deprecate 2 levels backtrace (to know it's called from clock or internaly)
18
18
  # remove lib/dynflow/clock.rb ClockReference#ping branch condition on removal.
19
- plan_event(event, nil, sent)
19
+ plan_event(event, nil, sent, optional: optional)
20
20
  end
21
21
 
22
22
  def <<(event = nil)
@@ -7,8 +7,8 @@ module Dynflow
7
7
  fail("Timeout exceeded.")
8
8
  end
9
9
 
10
- def schedule_timeout(seconds)
11
- plan_event(Timeout, seconds)
10
+ def schedule_timeout(seconds, optional: false)
11
+ plan_event(Timeout, seconds, optional: optional)
12
12
  end
13
13
  end
14
14
  end
data/lib/dynflow/actor.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  module Dynflow
3
3
 
4
+ FULL_BACKTRACE = %w[1 y yes].include?((ENV['DYNFLOW_FULL_BACKTRACE'] || '').downcase)
5
+ BACKTRACE_LIMIT = begin
6
+ limit = ENV['DYNFLOW_BACKTRACE_LIMIT'].to_i
7
+ limit.zero? ? nil : limit
8
+ end
9
+
4
10
  module MethodicActor
5
11
  def on_message(message)
6
12
  method, *args = message
@@ -44,7 +50,11 @@ module Dynflow
44
50
  include LogWithFullBacktrace
45
51
 
46
52
  def on_envelope(envelope)
47
- Actor::BacktraceCollector.with_backtrace(envelope.origin_backtrace) { super }
53
+ if FULL_BACKTRACE
54
+ Actor::BacktraceCollector.with_backtrace(envelope.origin_backtrace) { super }
55
+ else
56
+ super
57
+ end
48
58
  end
49
59
  end
50
60
 
@@ -83,9 +93,15 @@ module Dynflow
83
93
 
84
94
  # takes an array of backtrace lines and replaces each chunk
85
95
  def filter_backtrace(backtrace)
86
- backtrace.map { |line| filter_line(line) }
87
- .chunk_while { |l1, l2| l1 == l2}
88
- .map(&:first)
96
+ trace = backtrace.map { |line| filter_line(line) }
97
+ .chunk_while { |l1, l2| l1 == l2}
98
+ .map(&:first)
99
+ if BACKTRACE_LIMIT
100
+ count = trace.count
101
+ trace = trace.take(BACKTRACE_LIMIT)
102
+ trace << "[ backtrace omitted #{count - BACKTRACE_LIMIT} lines ]" if trace.count < count
103
+ end
104
+ trace
89
105
  end
90
106
  end
91
107
  end
data/lib/dynflow/clock.rb CHANGED
@@ -114,11 +114,11 @@ module Dynflow
114
114
  Time.now
115
115
  end
116
116
 
117
- def ping(who, time, with_what = nil, where = :<<)
117
+ def ping(who, time, with_what = nil, where = :<<, optional: false)
118
118
  Type! time, Time, Numeric
119
119
  time = current_time + time if time.is_a? Numeric
120
120
  if who.is_a?(Action::Suspended)
121
- who.plan_event(with_what, time)
121
+ who.plan_event(with_what, time, optional: optional)
122
122
  else
123
123
  timer = Clock::Timer[who, time, with_what.nil? ? Algebrick::Types::None : Some[Object][with_what], where]
124
124
  self.tell([:add_timer, timer])
@@ -15,7 +15,8 @@ module Dynflow
15
15
  execution_plan_id: String,
16
16
  step_id: Integer,
17
17
  event: Object,
18
- result: Concurrent::Promises::ResolvableFuture
18
+ result: Concurrent::Promises::ResolvableFuture,
19
+ optional: Algebrick::Types::Boolean
19
20
  end
20
21
 
21
22
  UnprocessableEvent = Class.new(Dynflow::Error)
@@ -163,6 +164,9 @@ module Dynflow
163
164
  execution_plan_manager = @execution_plan_managers[event.execution_plan_id]
164
165
  if execution_plan_manager
165
166
  execution_plan_manager.event(event)
167
+ elsif event.optional
168
+ event.result.reject "no manager for #{event.inspect}"
169
+ []
166
170
  else
167
171
  raise Dynflow::Error, "no manager for #{event.inspect}"
168
172
  end
@@ -20,8 +20,8 @@ module Dynflow
20
20
  def terminate
21
21
  pending_work = @work_items.clear.values.flatten(1)
22
22
  pending_work.each do |w|
23
- if EventWorkItem === w && w.event.result
24
- w.event.result.reject UnprocessableEvent.new("dropping due to termination")
23
+ finish_event_result(w) do |result|
24
+ result.reject UnprocessableEvent.new("dropping due to termination")
25
25
  end
26
26
  end
27
27
  end
@@ -6,7 +6,8 @@ module Dynflow
6
6
  fields! execution_plan_id: String,
7
7
  step_id: Integer,
8
8
  event: Object,
9
- time: type { variants Time, NilClass }
9
+ time: type { variants Time, NilClass },
10
+ optional: Algebrick::Types::Boolean
10
11
  end
11
12
 
12
13
  Execution = type do
@@ -132,11 +132,13 @@ module Dynflow
132
132
  end
133
133
 
134
134
  def dispatch_request(request, client_world_id, request_id)
135
+ ignore_unknown = false
135
136
  executor_id = match request,
136
137
  (on ~Execution do |execution|
137
138
  AnyExecutor
138
139
  end),
139
140
  (on ~Event do |event|
141
+ ignore_unknown = event.optional
140
142
  find_executor(event.execution_plan_id)
141
143
  end),
142
144
  (on Ping.(~any, ~any) | Status.(~any, ~any) do |receiver_id, _|
@@ -144,7 +146,11 @@ module Dynflow
144
146
  end)
145
147
  envelope = Envelope[request_id, client_world_id, executor_id, request]
146
148
  if Dispatcher::UnknownWorld === envelope.receiver_id
147
- raise Dynflow::Error, "Could not find an executor for #{envelope}"
149
+ raise Dynflow::Error, "Could not find an executor for #{envelope}" unless ignore_unknown
150
+
151
+ message = "Could not find an executor for optional #{envelope}, discarding."
152
+ log(Logger::DEBUG, message)
153
+ return respond(envelope, Failed[message])
148
154
  end
149
155
  connector.send(envelope).value!
150
156
  rescue => e
@@ -52,12 +52,14 @@ module Dynflow
52
52
  end
53
53
  end
54
54
  if event_request.time.nil? || event_request.time < Time.now
55
- @world.executor.event(envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, future)
55
+ @world.executor.event(envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, future,
56
+ optional: event_request.optional)
56
57
  else
57
58
  @world.clock.ping(
58
59
  @world.executor,
59
60
  event_request.time,
60
- Director::Event[envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, Concurrent::Promises.resolvable_future],
61
+ Director::Event[envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, Concurrent::Promises.resolvable_future,
62
+ event_request.optional],
61
63
  :delayed_event
62
64
  )
63
65
  # resolves the future right away - currently we do not wait for the clock ping
@@ -12,7 +12,7 @@ module Dynflow
12
12
 
13
13
  module Event
14
14
  def inspect
15
- "#{Time.at(time).utc}: #{name}".tap { |s| s << " @ #{world_id}" if world_id }
15
+ ["#{Time.at(time).utc}: #{name}", world_id].compact.join(' @ ')
16
16
  end
17
17
  end
18
18
 
@@ -418,6 +418,14 @@ module Dynflow
418
418
  end
419
419
  end
420
420
 
421
+ def self.load_flow(flow_hash)
422
+ if flow_hash.is_a? Hash
423
+ Flows::Abstract.from_hash(flow_hash)
424
+ else
425
+ Flows::Abstract.decode(flow_hash)
426
+ end
427
+ end
428
+
421
429
  def to_hash
422
430
  recursive_to_hash id: id,
423
431
  class: self.class.to_s,
@@ -425,8 +433,8 @@ module Dynflow
425
433
  state: state,
426
434
  result: result,
427
435
  root_plan_step_id: root_plan_step && root_plan_step.id,
428
- run_flow: run_flow,
429
- finalize_flow: finalize_flow,
436
+ run_flow: run_flow.encode,
437
+ finalize_flow: finalize_flow.encode,
430
438
  step_ids: steps.map { |id, _| id },
431
439
  started_at: time_to_str(started_at),
432
440
  ended_at: time_to_str(ended_at),
@@ -448,8 +456,8 @@ module Dynflow
448
456
  hash[:label],
449
457
  hash[:state],
450
458
  steps[hash[:root_plan_step_id]],
451
- Flows::Abstract.from_hash(hash[:run_flow]),
452
- Flows::Abstract.from_hash(hash[:finalize_flow]),
459
+ load_flow(hash[:run_flow]),
460
+ load_flow(hash[:finalize_flow]),
453
461
  steps,
454
462
  string_to_time(hash[:started_at]),
455
463
  string_to_time(hash[:ended_at]),
@@ -37,7 +37,7 @@ module Dynflow
37
37
 
38
38
  def plan_events(delayed_events)
39
39
  delayed_events.each do |event|
40
- @world.plan_event(event.execution_plan_id, event.step_id, event.event, event.time)
40
+ @world.plan_event(event.execution_plan_id, event.step_id, event.event, event.time, optional: event.optional)
41
41
  end
42
42
  end
43
43
 
@@ -33,8 +33,8 @@ module Dynflow
33
33
  raise e
34
34
  end
35
35
 
36
- def event(request_id, execution_plan_id, step_id, event, future = nil)
37
- @core.ask([:handle_event, Director::Event[request_id, execution_plan_id, step_id, event, future]])
36
+ def event(request_id, execution_plan_id, step_id, event, future = nil, optional: false)
37
+ @core.ask([:handle_event, Director::Event[request_id, execution_plan_id, step_id, event, future, optional]])
38
38
  future
39
39
  end
40
40
 
data/lib/dynflow/flows.rb CHANGED
@@ -4,6 +4,7 @@ require 'forwardable'
4
4
  module Dynflow
5
5
  module Flows
6
6
 
7
+ require 'dynflow/flows/registry'
7
8
  require 'dynflow/flows/abstract'
8
9
  require 'dynflow/flows/atom'
9
10
  require 'dynflow/flows/abstract_composed'
@@ -32,6 +32,20 @@ module Dynflow
32
32
  def flatten!
33
33
  raise NotImplementedError
34
34
  end
35
+
36
+ def self.new_from_hash(hash)
37
+ check_class_matching hash
38
+ new(hash[:flows].map { |flow_hash| from_hash(flow_hash) })
39
+ end
40
+
41
+ def self.decode(data)
42
+ if data.is_a? Integer
43
+ Flows::Atom.new(data)
44
+ else
45
+ kind, *subflows = data
46
+ Registry.decode(kind).new(subflows.map { |subflow| self.decode(subflow) })
47
+ end
48
+ end
35
49
  end
36
50
  end
37
51
  end
@@ -11,8 +11,8 @@ module Dynflow
11
11
  @flows = flows
12
12
  end
13
13
 
14
- def to_hash
15
- super.merge recursive_to_hash(:flows => flows)
14
+ def encode
15
+ [Registry.encode(self)] + flows.map(&:encode)
16
16
  end
17
17
 
18
18
  def <<(v)
@@ -61,11 +61,6 @@ module Dynflow
61
61
 
62
62
  protected
63
63
 
64
- def self.new_from_hash(hash)
65
- check_class_matching hash
66
- new(hash[:flows].map { |flow_hash| from_hash(flow_hash) })
67
- end
68
-
69
64
  # adds the +new_flow+ in a way that it's in sequence with
70
65
  # the +satisfying_flows+
71
66
  def add_to_sequence(satisfying_flows, new_flow)
@@ -5,8 +5,8 @@ module Dynflow
5
5
 
6
6
  attr_reader :step_id
7
7
 
8
- def to_hash
9
- super.merge(:step_id => step_id)
8
+ def encode
9
+ step_id
10
10
  end
11
11
 
12
12
  def initialize(step_id)
@@ -25,5 +25,7 @@ module Dynflow
25
25
  return Concurrence.new(extracted_sub_flows)
26
26
  end
27
27
  end
28
+
29
+ Registry.register!(Concurrence, 'C')
28
30
  end
29
31
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Dynflow
3
+ module Flows
4
+ class Registry
5
+ class IdentifierTaken < ArgumentError; end
6
+ class UnknownIdentifier < ArgumentError; end
7
+
8
+ class << self
9
+ def register!(klass, identifier)
10
+ if (found = serialization_map[identifier])
11
+ raise IdentifierTaken, "Error setting up mapping #{identifier} to #{klass}, it already maps to #{found}"
12
+ else
13
+ serialization_map.update(identifier => klass)
14
+ end
15
+ end
16
+
17
+ def encode(klass)
18
+ klass = klass.class unless klass.is_a?(Class)
19
+ serialization_map.invert[klass] || raise(UnknownIdentifier, "Could not find mapping for #{klass}")
20
+ end
21
+
22
+ def decode(identifier)
23
+ serialization_map[identifier] || raise(UnknownIdentifier, "Could not find mapping for #{identifier}")
24
+ end
25
+
26
+ def serialization_map
27
+ @serialization_map ||= {}
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -10,5 +10,7 @@ module Dynflow
10
10
  self << dependent_flow
11
11
  end
12
12
  end
13
+
14
+ Registry.register!(Sequence, 'S')
13
15
  end
14
16
  end
@@ -394,7 +394,7 @@ module Dynflow
394
394
 
395
395
  def dump_data(value)
396
396
  return if value.nil?
397
- MultiJson.dump Type!(value, Hash, Array)
397
+ MultiJson.dump Type!(value, Hash, Array, Integer)
398
398
  end
399
399
 
400
400
  def paginate(data_set, options)
@@ -34,8 +34,8 @@ module Dynflow
34
34
  @director.work_finished(work_item)
35
35
  end
36
36
 
37
- def event(execution_plan_id, step_id, event, future = Concurrent::Promises.resolvable_future)
38
- event = (Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, future])
37
+ def event(execution_plan_id, step_id, event, future = Concurrent::Promises.resolvable_future, optional: false)
38
+ event = (Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, future, optional])
39
39
  @director.handle_event(event).each do |work_item|
40
40
  @work_items << work_item
41
41
  end
@@ -58,15 +58,15 @@ module Dynflow
58
58
  future.reject e
59
59
  end
60
60
 
61
- def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future)
62
- @executor.event(execution_plan_id, step_id, event, done)
61
+ def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future, optional: false)
62
+ @executor.event(execution_plan_id, step_id, event, done, optional: optional)
63
63
  end
64
64
 
65
- def plan_event(execution_plan_id, step_id, event, time, done = Concurrent::Promises.resolvable_future)
65
+ def plan_event(execution_plan_id, step_id, event, time, done = Concurrent::Promises.resolvable_future, optional: false)
66
66
  if time.nil? || time < Time.now
67
- event(execution_plan_id, step_id, event, done)
67
+ event(execution_plan_id, step_id, event, done, optional: optional)
68
68
  else
69
- clock.ping(executor, time, Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, done], :delayed_event)
69
+ clock.ping(executor, time, Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, done, optional], :delayed_event)
70
70
  end
71
71
  end
72
72
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Dynflow
3
- VERSION = '1.4.7'
3
+ VERSION = '1.5.0'
4
4
  end
data/lib/dynflow/world.rb CHANGED
@@ -219,12 +219,12 @@ module Dynflow
219
219
  publish_request(Dispatcher::Execution[execution_plan_id], done, true)
220
220
  end
221
221
 
222
- def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future)
223
- publish_request(Dispatcher::Event[execution_plan_id, step_id, event], done, false)
222
+ def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future, optional: false)
223
+ publish_request(Dispatcher::Event[execution_plan_id, step_id, event, nil, optional], done, false)
224
224
  end
225
225
 
226
- def plan_event(execution_plan_id, step_id, event, time, accepted = Concurrent::Promises.resolvable_future)
227
- publish_request(Dispatcher::Event[execution_plan_id, step_id, event, time], accepted, false)
226
+ def plan_event(execution_plan_id, step_id, event, time, accepted = Concurrent::Promises.resolvable_future, optional: false)
227
+ publish_request(Dispatcher::Event[execution_plan_id, step_id, event, time, optional], accepted, false)
228
228
  end
229
229
 
230
230
  def ping(world_id, timeout, done = Concurrent::Promises.resolvable_future)
@@ -49,6 +49,12 @@ module Dynflow
49
49
  plan = result.finished.value
50
50
  assert_equal('finish', plan.actions.first.output[:event])
51
51
  end
52
+
53
+ it 'does not error on dispatching an optional event' do
54
+ request = client_world.event('123', 1, nil, optional: true)
55
+ request.wait(20)
56
+ assert_match /Could not find an executor for optional .*, discarding/, request.reason.message
57
+ end
52
58
  end
53
59
  end
54
60
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'test_helper'
3
+ require 'mocha/minitest'
4
+
5
+ module Dynflow
6
+ describe 'flow' do
7
+
8
+ class TestRegistry < Flows::Registry
9
+ class << self
10
+ def reset!
11
+ @serialization_map = {}
12
+ end
13
+ end
14
+ end
15
+
16
+ after do
17
+ TestRegistry.reset!
18
+ end
19
+
20
+ describe "registry" do
21
+ it "allows registering values" do
22
+ TestRegistry.register!(TestRegistry, 'TS')
23
+ TestRegistry.register!(Integer, 'I')
24
+ map = TestRegistry.instance_variable_get("@serialization_map")
25
+ _(map).must_equal({'TS' => TestRegistry, 'I' => Integer})
26
+ end
27
+
28
+ it "prevents overwriting values" do
29
+ TestRegistry.register!(Integer, 'I')
30
+ _(-> { TestRegistry.register!(Float, 'I') }).must_raise Flows::Registry::IdentifierTaken
31
+ end
32
+
33
+ it "encodes and decodes values" do
34
+ TestRegistry.register!(Integer, 'I')
35
+ _(TestRegistry.encode(Integer)).must_equal 'I'
36
+ end
37
+
38
+ it "raises an exception when unknown key is requested" do
39
+ _(-> { TestRegistry.encode(Float) }).must_raise Flows::Registry::UnknownIdentifier
40
+ _(-> { TestRegistry.decode('F') }).must_raise Flows::Registry::UnknownIdentifier
41
+ end
42
+ end
43
+ end
44
+ end
@@ -55,7 +55,7 @@ module Dynflow
55
55
 
56
56
  it 'delays the action' do
57
57
  _(execution_plan.steps.count).must_equal 1
58
- _(delayed_plan.start_at.inspect).must_equal (@start_at).inspect
58
+ _(delayed_plan.start_at).must_be_within_delta(@start_at, 0.5)
59
59
  _(history_names.call(execution_plan)).must_equal ['delay']
60
60
  end
61
61
 
@@ -86,7 +86,7 @@ module Dynflow
86
86
  original.each do |key, value|
87
87
  loaded_value = loaded[key.to_s]
88
88
  if value.is_a?(Time)
89
- _(loaded_value.inspect).must_equal value.inspect
89
+ _(loaded_value).must_be_within_delta(value, 0.5)
90
90
  elsif value.is_a?(Hash)
91
91
  assert_equal_attributes!(value, loaded_value)
92
92
  elsif value.nil?
@@ -348,7 +348,7 @@ module Dynflow
348
348
  if value.nil?
349
349
  assert_nil stored.fetch(name.to_sym)
350
350
  elsif value.is_a?(Time)
351
- _(stored.fetch(name.to_sym).inspect).must_equal value.inspect
351
+ _(stored.fetch(name.to_sym)).must_be_within_delta(value, 0.5)
352
352
  else
353
353
  _(stored.fetch(name.to_sym)).must_equal value
354
354
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.7
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Necas
8
8
  - Petr Chalupa
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 1970-01-01 00:00:00.000000000 Z
12
+ date: 2021-05-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -242,10 +242,11 @@ executables: []
242
242
  extensions: []
243
243
  extra_rdoc_files: []
244
244
  files:
245
+ - ".github/install_dependencies.sh"
246
+ - ".github/workflows/ruby.yml"
245
247
  - ".gitignore"
246
248
  - ".rubocop.yml"
247
249
  - ".rubocop_todo.yml"
248
- - ".travis.yml"
249
250
  - Dockerfile
250
251
  - Gemfile
251
252
  - MIT-LICENSE
@@ -474,6 +475,7 @@ files:
474
475
  - lib/dynflow/flows/abstract_composed.rb
475
476
  - lib/dynflow/flows/atom.rb
476
477
  - lib/dynflow/flows/concurrence.rb
478
+ - lib/dynflow/flows/registry.rb
477
479
  - lib/dynflow/flows/sequence.rb
478
480
  - lib/dynflow/logger_adapters.rb
479
481
  - lib/dynflow/logger_adapters/abstract.rb
@@ -576,11 +578,11 @@ files:
576
578
  - test/execution_plan_hooks_test.rb
577
579
  - test/execution_plan_test.rb
578
580
  - test/executor_test.rb
581
+ - test/flows_test.rb
579
582
  - test/future_execution_test.rb
580
583
  - test/memory_cosumption_watcher_test.rb
581
584
  - test/middleware_test.rb
582
585
  - test/persistence_test.rb
583
- - test/prepare_travis_env.sh
584
586
  - test/redis_locking_test.rb
585
587
  - test/rescue_test.rb
586
588
  - test/round_robin_test.rb
@@ -625,7 +627,7 @@ homepage: https://github.com/Dynflow/dynflow
625
627
  licenses:
626
628
  - MIT
627
629
  metadata: {}
628
- post_install_message:
630
+ post_install_message:
629
631
  rdoc_options: []
630
632
  require_paths:
631
633
  - lib
@@ -641,7 +643,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
641
643
  version: '0'
642
644
  requirements: []
643
645
  rubygems_version: 3.1.2
644
- signing_key:
646
+ signing_key:
645
647
  specification_version: 4
646
648
  summary: DYNamic workFLOW engine
647
649
  test_files:
@@ -659,11 +661,11 @@ test_files:
659
661
  - test/execution_plan_hooks_test.rb
660
662
  - test/execution_plan_test.rb
661
663
  - test/executor_test.rb
664
+ - test/flows_test.rb
662
665
  - test/future_execution_test.rb
663
666
  - test/memory_cosumption_watcher_test.rb
664
667
  - test/middleware_test.rb
665
668
  - test/persistence_test.rb
666
- - test/prepare_travis_env.sh
667
669
  - test/redis_locking_test.rb
668
670
  - test/rescue_test.rb
669
671
  - test/round_robin_test.rb
data/.travis.yml DELETED
@@ -1,33 +0,0 @@
1
- language: ruby
2
-
3
- services:
4
- - postgresql
5
- - redis
6
-
7
- rvm:
8
- - "2.3.1"
9
- - "2.4.0"
10
- - "2.5.0"
11
-
12
- env:
13
- global:
14
- - "TESTOPTS=--verbose DB=postgresql DB_CONN_STRING=postgres://postgres@localhost/travis_ci_test"
15
-
16
- matrix:
17
- include:
18
- - rvm: "2.4.0"
19
- env: "DB=mysql DB_CONN_STRING=mysql2://root@localhost/travis_ci_test"
20
- services:
21
- - mysql
22
- - redis
23
- - rvm: "2.4.0"
24
- env: "DB=sqlite3 DB_CONN_STRING=sqlite:/"
25
- - rvm: "2.4.0"
26
- env: "CONCURRENT_RUBY_EXT=true"
27
-
28
- install:
29
- - test/prepare_travis_env.sh
30
-
31
- script:
32
- - bundle exec rubocop
33
- - bundle exec rake test