dwf 0.1.3 → 0.1.7

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: 0057d6d96f647afa204d1ece11e64444df31b3db21c6ceda3ac92ac73c134105
4
- data.tar.gz: f7f1aff90493a9396a7bf13f9e7f6a3c348e32022e73e907ef8571b89c70efd6
3
+ metadata.gz: fb0f6f6f30b7ed1ba19120a4a1f77e450940e45433153de51f47df9f108d0fae
4
+ data.tar.gz: e0e86fdd169b7cbcdc811cf62f87b3043fbbd708bdf0f41fb006b392e4ab784b
5
5
  SHA512:
6
- metadata.gz: 9207d683e57e95e8fd3e555c2a589fb4be793a8a4cafc479e2446fdd1ab3e180b72154b4c4b8f93aca92bd06159ec8eb3df448ffce0b742bd647ec723088ef4d
7
- data.tar.gz: 41fdfb04643bdba4e7afda031308c19a347762aebc281fd42ac7f4be6be5d932a8a2fb610d38276338c07ac0b2835e3e12585796b7d39266e3a97976a39a4e7c
6
+ metadata.gz: 4a3770d4d7f63b7ede02d59e1ce43cb2634b39cbd9e037c920e4f816acb7b393de0f9030fda735e8ea0873f3679da120ff405f725e87c60c1b9eb6a7fbf355e3
7
+ data.tar.gz: 469e25cdf11441c59c80ff47197dcabcf40e0b086ab06babb6b39a0597483517c479ee9ea65767819b69afebf83db6dcdb5926a9839a2e40d02c03cf39a6eb57
@@ -0,0 +1,37 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ # Manually publish
5
+ workflow_dispatch:
6
+ # Alternatively, publish whenever changes are merged to the `main` branch.
7
+ push:
8
+ branches: [ master ]
9
+ paths:
10
+ - 'dwf.gemspec'
11
+
12
+ jobs:
13
+ build:
14
+ name: Build + Publish
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ packages: write
18
+ contents: read
19
+
20
+ steps:
21
+ - uses: actions/checkout@v2
22
+ - name: Set up Ruby 3.0.0
23
+ uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
24
+ with:
25
+ ruby-version: 3.0.0
26
+ - run: bundle install
27
+
28
+ - name: Publish to RubyGems
29
+ run: |
30
+ mkdir -p $HOME/.gem
31
+ touch $HOME/.gem/credentials
32
+ chmod 0600 $HOME/.gem/credentials
33
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
34
+ gem build *.gemspec
35
+ gem push *.gem
36
+ env:
37
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
@@ -0,0 +1,25 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - 'master'
7
+ pull_request:
8
+ branches:
9
+ - '**'
10
+
11
+ jobs:
12
+ test:
13
+
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
20
+ with:
21
+ ruby-version: 3.0.0
22
+ - name: Install dependencies
23
+ run: bundle install
24
+ - name: Run tests
25
+ run: bundle exec rspec --require spec_helper
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --format documentation
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+ ## 0.1.7
4
+ ### Added
5
+ - Allow to config redis and queue
6
+
7
+ ```ruby
8
+ Dwf.config do |config|
9
+ config.opts = { url 'redis://127.0.0.1:6379' }
10
+ config.namespace = 'dwf'
11
+ end
12
+ ```
13
+
14
+ ## 0.1.6
15
+ ### Added
16
+ - Sidekiq batch callback: separate batches
17
+
18
+ ## 0.1.5
19
+ ### Added
20
+ - add github action with build and public gem flow
21
+
22
+ ## 0.1.4
23
+ ### Added
24
+ - Add testes
25
+ - add github action
26
+
27
+ ### Fixed
28
+ - Remove Sidekiq pro by default
29
+
30
+ ---
31
+ ## 0.1.3
32
+ ### Added
33
+ - Support both build in and [Sidekiq batches](https://github.com/mperham/sidekiq/wiki/Batches) callback
34
+ - Update readme
35
+
36
+ ### Fixed
37
+ - Fix bug require development gem
38
+
39
+ ---
40
+ ## 0.1.2
41
+ ### Added
42
+ - Support [Sidekiq batches](https://github.com/mperham/sidekiq/wiki/Batches) callback
43
+ - Update readme
44
+
45
+ ### Fixed
46
+ - fix typo and remove development gem
47
+
48
+ ---
49
+ ## 0.1.0
50
+ ### Added
51
+ - init app with basic idea following [Gush](https://github.com/chaps-io/gush) concept
52
+ - Support build in callback
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- source "https://gems.contribsys.com/" do
6
- gem 'sidekiq-pro'
7
- end
5
+ # source "https://gems.contribsys.com/" do
6
+ # gem 'sidekiq-pro'
7
+ # end
8
8
 
data/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
  [Gush](https://github.com/chaps-io/gush) cloned without [ActiveJob](https://guides.rubyonrails.org/active_job_basics.html) but requried [Sidekiq](https://github.com/mperham/sidekiq). This project is for researching DSL purpose
3
3
 
4
4
  # Installation
5
+ ## 1. Add `dwf` to Gemfile
5
6
  ```ruby
6
- gem 'dwf', '~> 0.1.2'
7
+ gem 'dwf', '~> 0.1.6'
7
8
  ```
8
- # Execute flow
9
- ## Declare jobs
9
+ ## 2. Execute flow
10
+ ### Declare jobs
10
11
 
11
12
  ```ruby
12
13
  require 'dwf'
@@ -21,7 +22,7 @@ class A < Dwf::Item
21
22
  end
22
23
  ```
23
24
 
24
- ## Declare flow
25
+ ### Declare flow
25
26
  ```ruby
26
27
  require 'dwf'
27
28
 
@@ -35,18 +36,22 @@ class TestWf < Dwf::Workflow
35
36
  run F, params: 'F say hello'
36
37
  end
37
38
  end
38
-
39
- wf = TestWf.create
40
- wf.start!
41
-
42
39
  ```
43
40
 
41
+
44
42
  ### Execute flow
45
43
  ```ruby
46
- wf = TestWf.create
44
+ wf = TestWf.create(callback_type: Dwf::Workflow::SK_BATCH)
47
45
  wf.start!
48
46
  ```
49
47
 
48
+ #### Note
49
+ `dwf` supports 2 callback types `Dwf::Workflow::BUILD_IN` and `Dwf::Workflow::SK_BATCH`
50
+ - `Dwf::Workflow::BUILD_IN` is a build-in callback
51
+ - `Dwf::Workflow::SK_BATCH` is [sidekiq batch](https://github.com/mperham/sidekiq/wiki/Batches) callback which required [`sidekiq-pro`](https://sidekiq.org/products/pro.html)
52
+
53
+ By default `dwf` will use `Dwf::Workflow::BUILD_IN` callback.
54
+
50
55
  ### Output
51
56
  ```
52
57
  A Working
@@ -66,13 +71,28 @@ D say hello
66
71
  D Finished
67
72
  ```
68
73
 
74
+ # Config redis and default queue
75
+ ```ruby
76
+ Dwf.config do |config|
77
+ SENTINELS = [
78
+ { host: "127.0.0.1", port: 26380 },
79
+ { host: "127.0.0.1", port: 26381 }
80
+ ]
81
+ config.opts = { host: 'mymaster', sentinels: SENTINELS, role: :master }
82
+ config.namespace = 'dwf'
83
+ end
84
+ ```
85
+
86
+
69
87
  # Todo
70
88
  - [x] Make it work
71
89
  - [x] Support pass params
72
- - [ ] Support with build-in callback
73
- - [ ] Test
74
- - [ ] Add github workflow
90
+ - [x] Support with build-in callback
91
+ - [x] Add github workflow
92
+ - [x] Redis configurable
93
+ - [ ] [WIP] Test
75
94
  - [ ] Transfer output through each node
95
+ - [ ] Support [Resque](https://github.com/resque/resque)
76
96
 
77
97
  # References
78
98
  - https://github.com/chaps-io/gush
data/dwf.gemspec CHANGED
@@ -3,10 +3,11 @@
3
3
 
4
4
  lib = File.expand_path('../lib', __FILE__)
5
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require_relative 'lib/dwf/version'
6
7
 
7
8
  Gem::Specification.new do |spec|
8
9
  spec.name = "dwf"
9
- spec.version = '0.1.3'
10
+ spec.version = Dwf::VERSION
10
11
  spec.authors = ["dthtien"]
11
12
  spec.email = ["tiendt2311@gmail.com"]
12
13
 
@@ -27,5 +28,6 @@ Gem::Specification.new do |spec|
27
28
  spec.add_development_dependency 'byebug', '~> 11.1.3'
28
29
  spec.add_dependency 'redis', '~> 4.2.0'
29
30
  spec.add_development_dependency 'rspec', '~> 3.2'
31
+ spec.add_development_dependency 'mock_redis', '~> 0.27.2'
30
32
  spec.add_dependency 'sidekiq', '~> 6.2.0'
31
33
  end
data/lib/dwf/callback.rb CHANGED
@@ -1,8 +1,10 @@
1
- require 'sidekiq-pro'
1
+ # frozen_string_literal: true
2
2
  require_relative 'client'
3
3
 
4
4
  module Dwf
5
5
  class Callback
6
+ DEFAULT_KEY = 'default_key'
7
+
6
8
  def process_next_step(status, options)
7
9
  previous_job_names = options['names']
8
10
  workflow_id = options['workflow_id']
@@ -13,7 +15,7 @@ module Dwf
13
15
  return if processing_job_names.empty?
14
16
 
15
17
  overall = Sidekiq::Batch.new(status.parent_bid)
16
- overall.jobs { setup_batch(processing_job_names, workflow_id) }
18
+ overall.jobs { setup_batches(processing_job_names, workflow_id) }
17
19
  end
18
20
 
19
21
  def start(job)
@@ -22,25 +24,45 @@ module Dwf
22
24
 
23
25
  private
24
26
 
25
- def setup_batch(processing_job_names, workflow_id)
27
+ def setup_batches(processing_job_names, workflow_id)
28
+ jobs = fetch_jobs(processing_job_names, workflow_id)
29
+ jobs_classification = classify_jobs jobs
30
+
31
+ jobs_classification.each do |key, batch_jobs|
32
+ with_lock workflow_id, key do
33
+ setup_batch(batch_jobs, workflow_id)
34
+ end
35
+ end
36
+ end
37
+
38
+ def setup_batch(jobs, workflow_id)
26
39
  batch = Sidekiq::Batch.new
27
40
  batch.on(
28
41
  :success,
29
42
  'Dwf::Callback#process_next_step',
30
- names: processing_job_names,
43
+ names: jobs.map(&:klass),
31
44
  workflow_id: workflow_id
32
45
  )
33
-
34
46
  batch.jobs do
35
- processing_job_names.each { |job_name| perform_job(job_name, workflow_id) }
47
+ jobs.each { |job| job.persist_and_perform_async! if job.ready_to_start? }
36
48
  end
37
49
  end
38
50
 
39
- def perform_job(job_name, workflow_id)
40
- with_lock workflow_id, job_name do
41
- job = client.find_job(workflow_id, job_name)
42
- job.persist_and_perform_async! if job.ready_to_start?
51
+ def classify_jobs(jobs)
52
+ hash = {}
53
+ jobs.each do |job|
54
+ outgoing_jobs = job.outgoing
55
+ key = outgoing_jobs.empty? ? DEFAULT_KEY : outgoing_jobs.join
56
+ hash[key] = hash[key].nil? ? [job] : hash[key].push(job)
43
57
  end
58
+
59
+ hash
60
+ end
61
+
62
+ def fetch_jobs(processing_job_names, workflow_id)
63
+ processing_job_names.map do |job_name|
64
+ client.find_job(workflow_id, job_name)
65
+ end.compact
44
66
  end
45
67
 
46
68
  def with_lock(workflow_id, job_name)
data/lib/dwf/client.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  module Dwf
2
2
  class Client
3
+ attr_reader :config
4
+
5
+ def initialize(config = Dwf.configuration)
6
+ @config = config
7
+ end
8
+
3
9
  def find_job(workflow_id, job_name)
4
10
  job_name_match = /(?<klass>\w*[^-])-(?<identifier>.*)/.match(job_name)
5
11
  data = if job_name_match
@@ -94,7 +100,7 @@ module Dwf
94
100
  end
95
101
 
96
102
  def redis
97
- @redis ||= Redis.new
103
+ @redis ||= Redis.new(config.redis_opts)
98
104
  end
99
105
  end
100
106
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dwf
4
+ class Configuration
5
+ NAMESPACE = 'dwf'
6
+ REDIS_OPTS = { url: 'redis://localhost:6379' }.freeze
7
+
8
+ attr_accessor :redis_opts, :namespace
9
+
10
+ def initialize(hash = {})
11
+ @namespace = hash.fetch(:namespace, NAMESPACE)
12
+ @redis_opts = hash.fetch(:redis_url, REDIS_OPTS)
13
+ end
14
+ end
15
+ end
data/lib/dwf/item.rb CHANGED
@@ -1,19 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'client'
2
4
 
3
5
  module Dwf
4
6
  class Item
5
7
  attr_reader :workflow_id, :id, :params, :queue, :klass, :started_at,
6
8
  :enqueued_at, :finished_at, :failed_at, :callback_type
7
- attr_accessor :incomming, :outgoing
9
+ attr_accessor :incoming, :outgoing
8
10
 
9
11
  def initialize(options = {})
10
12
  @workflow_id = options[:workflow_id]
11
13
  @id = options[:id]
12
14
  @params = options[:params]
13
- @queue = options[:queue] || 'default'
14
- @incomming = options[:incoming] || []
15
+ @queue = options[:queue]
16
+ @incoming = options[:incoming] || []
15
17
  @outgoing = options[:outgoing] || []
16
18
  @klass = options[:klass] || self.class
19
+ @failed_at = options[:failed_at]
17
20
  @finished_at = options[:finished_at]
18
21
  @enqueued_at = options[:enqueued_at]
19
22
  @started_at = options[:started_at]
@@ -37,7 +40,8 @@ module Dwf
37
40
  end
38
41
 
39
42
  def perform_async
40
- Dwf::Worker.set(queue: queue).perform_async(workflow_id, name)
43
+ Dwf::Worker.set(queue: queue || client.config.namespace)
44
+ .perform_async(workflow_id, name)
41
45
  end
42
46
 
43
47
  def name
@@ -45,11 +49,11 @@ module Dwf
45
49
  end
46
50
 
47
51
  def no_dependencies?
48
- incomming.empty?
52
+ incoming.empty?
49
53
  end
50
54
 
51
55
  def parents_succeeded?
52
- incomming.all? do |name|
56
+ incoming.all? do |name|
53
57
  client.find_job(workflow_id, name).succeeded?
54
58
  end
55
59
  end
@@ -130,7 +134,7 @@ module Dwf
130
134
  id: id,
131
135
  klass: klass.to_s,
132
136
  queue: queue,
133
- incoming: incomming,
137
+ incoming: incoming,
134
138
  outgoing: outgoing,
135
139
  finished_at: finished_at,
136
140
  enqueued_at: enqueued_at,
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dwf
4
+ VERSION = '0.1.7'
5
+ end
data/lib/dwf/worker.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sidekiq'
2
4
  require_relative 'client'
3
5
 
data/lib/dwf/workflow.rb CHANGED
@@ -157,7 +157,7 @@ module Dwf
157
157
  from = find_job(dependency[:from])
158
158
  to = find_job(dependency[:to])
159
159
 
160
- to.incomming << dependency[:from]
160
+ to.incoming << dependency[:from]
161
161
  from.outgoing << dependency[:to]
162
162
  end
163
163
  end
data/lib/dwf.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "bundler/setup"
3
4
 
4
5
  require 'sidekiq'
5
6
  require 'json'
6
7
  require 'redis'
8
+ # require 'sidekiq-pro'
7
9
 
8
10
  require_relative 'dwf/utils'
9
11
  require_relative 'dwf/workflow'
@@ -11,8 +13,15 @@ require_relative 'dwf/item'
11
13
  require_relative 'dwf/client'
12
14
  require_relative 'dwf/worker'
13
15
  require_relative 'dwf/callback'
16
+ require_relative 'dwf/configuration'
14
17
 
15
18
  module Dwf
16
- VERSION = '0.1.0'
19
+ def self.configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def self.config
24
+ yield configuration
25
+ end
17
26
  end
18
27
 
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'mock_redis'
5
+
6
+ describe Dwf::Client, client: true do
7
+ let(:client) { described_class.new }
8
+ let(:workflow_id) { SecureRandom.uuid }
9
+ let(:id) { SecureRandom.uuid }
10
+ let(:redis) { Redis.new }
11
+ before do
12
+ redis_instance = MockRedis.new
13
+ allow(Redis).to receive(:new).and_return redis_instance
14
+ end
15
+
16
+ describe '#find_job' do
17
+ let!(:job) do
18
+ j = Dwf::Item.new(workflow_id: workflow_id, id: id)
19
+ j.persist!
20
+ j
21
+ end
22
+
23
+ context 'find by item class name' do
24
+ it {
25
+ item = client.find_job(workflow_id, Dwf::Item.name)
26
+ expect(item.workflow_id).to eq workflow_id
27
+ expect(item.id).to eq id
28
+ expect(item.name).to eq job.name
29
+ }
30
+ end
31
+
32
+ context 'find by item name' do
33
+ it {
34
+ item = client.find_job(workflow_id, job.name)
35
+ expect(item.workflow_id).to eq workflow_id
36
+ expect(item.id).to eq id
37
+ expect(item.name).to eq job.name
38
+ }
39
+ end
40
+ end
41
+
42
+ describe '#persist_job' do
43
+ let!(:job) { Dwf::Item.new(workflow_id: workflow_id, id: id) }
44
+
45
+ it do
46
+ expect(redis.exists?("dwf.jobs.#{job.workflow_id}.#{job.klass}"))
47
+ .to be_falsy
48
+
49
+ client.persist_job(job)
50
+
51
+ expect(redis.exists?("dwf.jobs.#{job.workflow_id}.#{job.klass}"))
52
+ .to be_truthy
53
+ end
54
+ end
55
+
56
+ describe '#persist_workflow' do
57
+ let(:workflow) { Dwf::Workflow.new }
58
+
59
+ it do
60
+ expect(redis.exists?("dwf.workflows.#{workflow.id}")).to be_falsy
61
+ client.persist_workflow(workflow)
62
+ expect(redis.exists?("dwf.workflows.#{workflow.id}")).to be_truthy
63
+ end
64
+ end
65
+
66
+ describe '#check_or_lock' do
67
+ before do
68
+ allow_any_instance_of(described_class).to receive(:sleep)
69
+ end
70
+
71
+ context 'job is running' do
72
+ let(:job_name) { 'ahihi' }
73
+
74
+ before do
75
+ allow(client).to receive(:set)
76
+ redis.set("wf_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}", 'running')
77
+ client.check_or_lock(workflow_id, job_name)
78
+ end
79
+
80
+ it { expect(client).not_to have_received(:set) }
81
+ end
82
+
83
+ context 'job is not running' do
84
+ let(:job_name) { 'ahihi' }
85
+
86
+ before do
87
+ allow(redis).to receive(:set)
88
+ client.check_or_lock(workflow_id, job_name)
89
+ end
90
+
91
+ it do
92
+ expect(redis).to have_received(:set)
93
+ .with("wf_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}", 'running')
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#release_lock' do
99
+ before do
100
+ allow(redis).to receive(:del)
101
+ client.release_lock(workflow_id, 'ahihi')
102
+ end
103
+
104
+ it do
105
+ expect(redis).to have_received(:del)
106
+ .with("dwf_enqueue_outgoing_jobs_#{workflow_id}-ahihi")
107
+ end
108
+ end
109
+
110
+ describe '#build_job_id' do
111
+ before do
112
+ allow(redis).to receive(:hexists)
113
+ client.build_job_id(workflow_id, 'ahihi')
114
+ end
115
+
116
+ it { expect(redis).to have_received(:hexists) }
117
+ end
118
+
119
+ describe '#build_workflow_id' do
120
+ before do
121
+ allow(redis).to receive(:exists?)
122
+ client.build_workflow_id
123
+ end
124
+
125
+ it { expect(redis).to have_received(:exists?) }
126
+ end
127
+
128
+ describe '#key_exists?' do
129
+ before do
130
+ allow(redis).to receive(:exists?)
131
+ client.key_exists?('ahihi')
132
+ end
133
+
134
+ it { expect(redis).to have_received(:exists?).with('ahihi') }
135
+ end
136
+
137
+ describe '#set' do
138
+ before do
139
+ allow(redis).to receive(:set)
140
+ client.set('ahihi', 'a')
141
+ end
142
+
143
+ it { expect(redis).to have_received(:set).with('ahihi', 'a') }
144
+ end
145
+
146
+ describe '#delete' do
147
+ before do
148
+ allow(redis).to receive(:del)
149
+ client.delete('ahihi')
150
+ end
151
+
152
+ it { expect(redis).to have_received(:del).with('ahihi') }
153
+ end
154
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Dwf::Configuration, configuration: true do
6
+ let(:configuration) { described_class.new }
7
+
8
+ specify do
9
+ expect(configuration.namespace).to eq described_class::NAMESPACE
10
+ expect(configuration.redis_opts).to eq described_class::REDIS_OPTS
11
+ end
12
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Dwf::Item, item: true do
6
+ let!(:id) { SecureRandom.uuid }
7
+ let!(:workflow_id) { SecureRandom.uuid }
8
+ let(:incoming) { [] }
9
+ let(:outgoing) { [] }
10
+ let(:started_at) { nil }
11
+ let(:finished_at) { nil }
12
+ let(:options) do
13
+ {
14
+ workflow_id: workflow_id,
15
+ id: id,
16
+ params: {},
17
+ incoming: incoming,
18
+ outgoing: outgoing,
19
+ queue: Dwf::Configuration::NAMESPACE,
20
+ klass: 'Dwf::Item',
21
+ started_at: started_at,
22
+ finished_at: finished_at,
23
+ callback_type: Dwf::Workflow::BUILD_IN
24
+ }
25
+ end
26
+ let!(:item) { described_class.new(options) }
27
+
28
+ describe 'self.from_hash' do
29
+ let!(:item) { described_class.from_hash(options) }
30
+ it { expect(item.to_hash.compact).to eq options.compact }
31
+ end
32
+
33
+ describe '#persist_and_perform_async!' do
34
+ let(:worker_double) { double(perform_async: nil) }
35
+ let(:client_double) { double(persist_job: nil) }
36
+
37
+ before do
38
+ allow(Dwf::Client).to receive(:new).and_return client_double
39
+ allow(Dwf::Worker).to receive(:set).and_return worker_double
40
+ item.persist_and_perform_async!
41
+ end
42
+
43
+ it do
44
+ expect(worker_double)
45
+ .to have_received(:perform_async)
46
+ .with(item.workflow_id, item.name)
47
+ expect(client_double).to have_received(:persist_job).with(item)
48
+ expect(item.enqueued_at).not_to be_nil
49
+ end
50
+ end
51
+
52
+ describe '#cb_build_in?' do
53
+ it { expect(item.cb_build_in?).to be_truthy }
54
+ end
55
+
56
+ describe '#perform_async' do
57
+ let(:worker_double) { double(perform_async: nil) }
58
+ before do
59
+ allow(Dwf::Worker).to receive(:set).and_return worker_double
60
+ item.perform_async
61
+ end
62
+
63
+ it do
64
+ expect(worker_double)
65
+ .to have_received(:perform_async)
66
+ .with(item.workflow_id, item.name)
67
+ end
68
+ end
69
+
70
+ describe '#name' do
71
+ it { expect(item.name).to eq "#{described_class}|#{id}" }
72
+ end
73
+
74
+ describe '#no_dependencies?' do
75
+ it { expect(item.no_dependencies?).to be_truthy }
76
+ end
77
+
78
+ describe '#parents_succeeded?' do
79
+ let(:incoming) { ["A|#{SecureRandom.uuid}"] }
80
+ let(:client_double) { double(find_job: nil) }
81
+ let(:a_item) do
82
+ described_class.new(
83
+ workflow_id: SecureRandom.uuid,
84
+ id: SecureRandom.uuid,
85
+ finished_at: finished_at
86
+ )
87
+ end
88
+
89
+ before do
90
+ allow(Dwf::Client).to receive(:new).and_return client_double
91
+ allow(client_double)
92
+ .to receive(:find_job).and_return a_item
93
+ end
94
+
95
+ context 'parent jobs already finished' do
96
+ let(:finished_at) { Time.now.to_i }
97
+
98
+ it do
99
+ expect(item.parents_succeeded?).to be_truthy
100
+ expect(client_double)
101
+ .to have_received(:find_job)
102
+ .with(workflow_id, incoming.first)
103
+ end
104
+ end
105
+
106
+ context 'parent jobs havent finished yet' do
107
+ let(:finished_at) { nil }
108
+
109
+ it do
110
+ expect(item.parents_succeeded?).to be_falsy
111
+ expect(client_double)
112
+ .to have_received(:find_job)
113
+ .with(workflow_id, incoming.first)
114
+ end
115
+ end
116
+ end
117
+
118
+ describe '#enqueue!' do
119
+ before { item.enqueue! }
120
+
121
+ it { expect(item.enqueued_at).not_to be_nil }
122
+ end
123
+
124
+ describe '#mark_as_started' do
125
+ let(:client_double) { double(persist_job: nil) }
126
+ before do
127
+ allow(Dwf::Client).to receive(:new).and_return client_double
128
+ item.mark_as_started
129
+ end
130
+
131
+ it do
132
+ expect(client_double).to have_received(:persist_job).with item
133
+ expect(item.started_at).not_to be_nil
134
+ end
135
+ end
136
+
137
+ describe '#mark_as_finished' do
138
+ let(:client_double) { double(persist_job: nil) }
139
+ before do
140
+ allow(Dwf::Client).to receive(:new).and_return client_double
141
+ item.mark_as_finished
142
+ end
143
+
144
+ it do
145
+ expect(client_double).to have_received(:persist_job).with item
146
+ expect(item.finished_at).not_to be_nil
147
+ end
148
+ end
149
+
150
+ describe '#enqueue_outgoing_jobs' do
151
+ let(:outgoing) { ["A|#{SecureRandom.uuid}"] }
152
+ let(:client_double) do
153
+ double(
154
+ find_job: nil,
155
+ check_or_lock: nil,
156
+ release_lock: nil
157
+ )
158
+ end
159
+ let(:a_item) do
160
+ described_class.new(
161
+ workflow_id: SecureRandom.uuid,
162
+ id: SecureRandom.uuid,
163
+ started_at: started_at
164
+ )
165
+ end
166
+ before do
167
+ allow(Dwf::Client).to receive(:new).and_return client_double
168
+ allow(a_item).to receive(:persist_and_perform_async!)
169
+ allow(client_double)
170
+ .to receive(:find_job).and_return a_item
171
+ item.enqueue_outgoing_jobs
172
+ end
173
+
174
+ context 'outgoing jobs ready to start' do
175
+ let(:started_at) { nil }
176
+ it { expect(a_item).to have_received(:persist_and_perform_async!) }
177
+ end
178
+
179
+ context 'outgoing jobs havent ready to start' do
180
+ let(:started_at) { Time.now.to_i }
181
+ it { expect(a_item).not_to have_received(:persist_and_perform_async!) }
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dwf::Utils, utils: true do
4
+ describe '#symbolize_keys' do
5
+ let(:expected) do
6
+ {
7
+ incoming: ['S'],
8
+ outgoing: %w[A B],
9
+ klass: 'H'
10
+ }
11
+ end
12
+
13
+ let(:hash) do
14
+ {
15
+ 'incoming' => ['S'],
16
+ 'outgoing' => %w[A B],
17
+ 'klass' => 'H'
18
+ }
19
+ end
20
+
21
+ it { expect(described_class.symbolize_keys(hash)).to eq expected }
22
+ end
23
+ end
@@ -0,0 +1,101 @@
1
+ require 'dwf'
2
+ # This file was generated by the `rspec --init` command. Conventionally, all
3
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
4
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
5
+ # this file to always be loaded, without a need to explicitly require it in any
6
+ # files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, consider making
12
+ # a separate helper file that requires the additional dependencies and performs
13
+ # the additional setup, and require it from the spec files that actually need
14
+ # it.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # This option will default to `true` in RSpec 4. It makes the `description`
23
+ # and `failure_message` of custom matchers include text for helper methods
24
+ # defined using `chain`, e.g.:
25
+ # be_bigger_than(2).and_smaller_than(4).description
26
+ # # => "be bigger than 2 and smaller than 4"
27
+ # ...rather than:
28
+ # # => "be bigger than 2"
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ # rspec-mocks config goes here. You can use an alternate test double
33
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
34
+ config.mock_with :rspec do |mocks|
35
+ # Prevents you from mocking or stubbing a method that does not exist on
36
+ # a real object. This is generally recommended, and will default to
37
+ # `true` in RSpec 4.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
42
+ # have no way to turn it off -- the option exists only for backwards
43
+ # compatibility in RSpec 3). It causes shared context metadata to be
44
+ # inherited by the metadata hash of host groups and examples, rather than
45
+ # triggering implicit auto-inclusion in groups with matching metadata.
46
+ config.shared_context_metadata_behavior = :apply_to_host_groups
47
+
48
+ # The settings below are suggested to provide a good initial experience
49
+ # with RSpec, but feel free to customize to your heart's content.
50
+ =begin
51
+ # This allows you to limit a spec run to individual examples or groups
52
+ # you care about by tagging them with `:focus` metadata. When nothing
53
+ # is tagged with `:focus`, all examples get run. RSpec also provides
54
+ # aliases for `it`, `describe`, and `context` that include `:focus`
55
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
56
+ config.filter_run_when_matching :focus
57
+
58
+ # Allows RSpec to persist some state between runs in order to support
59
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
60
+ # you configure your source control system to ignore this file.
61
+ config.example_status_persistence_file_path = "spec/examples.txt"
62
+
63
+ # Limits the available syntax to the non-monkey patched syntax that is
64
+ # recommended. For more details, see:
65
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
66
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
67
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
68
+ config.disable_monkey_patching!
69
+
70
+ # This setting enables warnings. It's recommended, but in some cases may
71
+ # be too noisy due to issues in dependencies.
72
+ config.warnings = true
73
+
74
+ # Many RSpec users commonly either run the entire suite or an individual
75
+ # file, and it's useful to allow more verbose output when running an
76
+ # individual spec file.
77
+ if config.files_to_run.one?
78
+ # Use the documentation formatter for detailed output,
79
+ # unless a formatter has already been configured
80
+ # (e.g. via a command-line flag).
81
+ config.default_formatter = "doc"
82
+ end
83
+
84
+ # Print the 10 slowest examples and example groups at the
85
+ # end of the spec run, to help surface which specs are running
86
+ # particularly slow.
87
+ config.profile_examples = 10
88
+
89
+ # Run specs in random order to surface order dependencies. If you find an
90
+ # order dependency and want to debug it, you can fix the order by providing
91
+ # the seed, which is printed after each run.
92
+ # --seed 1234
93
+ config.order = :random
94
+
95
+ # Seed global randomization in this process using the `--seed` CLI option.
96
+ # Setting this allows you to use `--seed` to deterministically reproduce
97
+ # test failures related to randomization by passing the same `--seed` value
98
+ # as the one that triggered the failure.
99
+ Kernel.srand config.seed
100
+ =end
101
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dwf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - dthtien
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-04 00:00:00.000000000 Z
11
+ date: 2021-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mock_redis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.27.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.27.2
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: sidekiq
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -73,8 +87,12 @@ executables: []
73
87
  extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
90
+ - ".github/workflows/build_gem.yaml"
91
+ - ".github/workflows/test.yaml"
76
92
  - ".gitignore"
93
+ - ".rspec"
77
94
  - ".ruby-version"
95
+ - CHANGELOG.md
78
96
  - Gemfile
79
97
  - LICENSE.txt
80
98
  - README.md
@@ -82,15 +100,22 @@ files:
82
100
  - lib/dwf.rb
83
101
  - lib/dwf/callback.rb
84
102
  - lib/dwf/client.rb
103
+ - lib/dwf/configuration.rb
85
104
  - lib/dwf/item.rb
86
105
  - lib/dwf/utils.rb
106
+ - lib/dwf/version.rb
87
107
  - lib/dwf/worker.rb
88
108
  - lib/dwf/workflow.rb
109
+ - spec/dwf/client_spec.rb
110
+ - spec/dwf/configuration_spec.rb
111
+ - spec/dwf/item_spec.rb
112
+ - spec/dwf/utils_spec.rb
113
+ - spec/spec_helper.rb
89
114
  homepage: https://github.com/dthtien/wf
90
115
  licenses:
91
116
  - MIT
92
117
  metadata: {}
93
- post_install_message:
118
+ post_install_message:
94
119
  rdoc_options: []
95
120
  require_paths:
96
121
  - lib
@@ -106,8 +131,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
131
  version: '0'
107
132
  requirements: []
108
133
  rubygems_version: 3.2.3
109
- signing_key:
134
+ signing_key:
110
135
  specification_version: 4
111
136
  summary: Gush cloned without ActiveJob but requried Sidekiq. This project is for researching
112
137
  DSL purpose
113
- test_files: []
138
+ test_files:
139
+ - spec/dwf/client_spec.rb
140
+ - spec/dwf/configuration_spec.rb
141
+ - spec/dwf/item_spec.rb
142
+ - spec/dwf/utils_spec.rb
143
+ - spec/spec_helper.rb