zspec 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +53 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +119 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +62 -0
- data/README.md +33 -0
- data/bin/zspec +7 -0
- data/docker-compose.yaml +41 -0
- data/hack/client/entrypoint.sh +13 -0
- data/hack/worker/entrypoint.sh +10 -0
- data/lib/zspec.rb +9 -0
- data/lib/zspec/cli.rb +113 -0
- data/lib/zspec/formatter.rb +80 -0
- data/lib/zspec/presenter.rb +172 -0
- data/lib/zspec/queue.rb +138 -0
- data/lib/zspec/scheduler.rb +42 -0
- data/lib/zspec/sink.rb +1 -0
- data/lib/zspec/sink/memory_sink.rb +86 -0
- data/lib/zspec/tracker.rb +101 -0
- data/lib/zspec/util.rb +23 -0
- data/lib/zspec/version.rb +3 -0
- data/lib/zspec/worker.rb +38 -0
- data/workflow.png +0 -0
- data/zspec.gemspec +33 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4f91c82c47a083a827a3e2e29ccea3fe268caa788f27a9843235b43436967ae0
|
4
|
+
data.tar.gz: ec4a42c998105102542fa273e8e8a3c26ca45f8b09ae70a6d543ce7d011b04a7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e9adcfcc46c6d1a0da6ff8ef7aa6168732b7b7a45b740f2e68891c58151b18549fdcdd2c1accd36724527c71d2629392a55f4ec1dfca2a9509a18a0d28a38a67
|
7
|
+
data.tar.gz: e54d0c1373e177135aadc41f9e43381506b9ddbde4bd0f9ad41ce307c4c5823c9971e44528275ea584083b5a009f114c8b607ad918360744ac2cd5df5ef81626
|
data/.drone.yml
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
kind: pipeline
|
2
|
+
name: images
|
3
|
+
type: kubernetes
|
4
|
+
|
5
|
+
steps:
|
6
|
+
- name: bundle
|
7
|
+
image: ruby:2.6
|
8
|
+
volumes:
|
9
|
+
- name: ruby
|
10
|
+
path: /usr/local/bundle
|
11
|
+
commands:
|
12
|
+
- bundle install
|
13
|
+
|
14
|
+
- name: worker
|
15
|
+
image: ruby:2.6
|
16
|
+
detach: true
|
17
|
+
environment:
|
18
|
+
ZSPEC_REDIS_HOST: redis
|
19
|
+
ZSPEC_REDIS_PORT: 6379
|
20
|
+
ZSPEC_BUILD_NUMBER: ${DRONE_BUILD_NUMBER}
|
21
|
+
volumes:
|
22
|
+
- name: ruby
|
23
|
+
path: /usr/local/bundle
|
24
|
+
commands:
|
25
|
+
- ./hack/worker/entrypoint.sh
|
26
|
+
|
27
|
+
- name: client
|
28
|
+
image: ruby:2.6
|
29
|
+
environment:
|
30
|
+
ZSPEC_REDIS_HOST: redis
|
31
|
+
ZSPEC_REDIS_PORT: 6379
|
32
|
+
ZSPEC_BUILD_NUMBER: ${DRONE_BUILD_NUMBER}
|
33
|
+
volumes:
|
34
|
+
- name: ruby
|
35
|
+
path: /usr/local/bundle
|
36
|
+
commands:
|
37
|
+
- ./hack/client/entrypoint.sh
|
38
|
+
|
39
|
+
services:
|
40
|
+
- name: redis
|
41
|
+
image: redis
|
42
|
+
ports:
|
43
|
+
- 6379
|
44
|
+
|
45
|
+
volumes:
|
46
|
+
- name: ruby
|
47
|
+
temp: {}
|
48
|
+
|
49
|
+
trigger:
|
50
|
+
event:
|
51
|
+
- pull_request
|
52
|
+
branch:
|
53
|
+
- master
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-fd -c
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
AllCops:
|
2
|
+
ExtraDetails: true
|
3
|
+
TargetRubyVersion: 2.6
|
4
|
+
|
5
|
+
Style/BlockDelimiters:
|
6
|
+
EnforcedStyle: braces_for_chaining
|
7
|
+
|
8
|
+
Layout/FirstHashElementIndentation:
|
9
|
+
EnforcedStyle: consistent
|
10
|
+
|
11
|
+
Layout/ArgumentAlignment:
|
12
|
+
EnforcedStyle: with_fixed_indentation
|
13
|
+
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Max: 25
|
16
|
+
|
17
|
+
Metrics/LineLength:
|
18
|
+
Max: 120
|
19
|
+
|
20
|
+
Metrics/ModuleLength:
|
21
|
+
Max: 250
|
22
|
+
|
23
|
+
Metrics/ClassLength:
|
24
|
+
Max: 250
|
25
|
+
|
26
|
+
Metrics/MethodLength:
|
27
|
+
Max: 30
|
28
|
+
|
29
|
+
Metrics/BlockLength:
|
30
|
+
Exclude:
|
31
|
+
- "spec/**/*"
|
32
|
+
|
33
|
+
Metrics/CyclomaticComplexity:
|
34
|
+
Max: 7
|
35
|
+
|
36
|
+
Style/AsciiComments:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Style/ClassAndModuleChildren:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Style/Documentation:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Style/FormatString:
|
46
|
+
EnforcedStyle: sprintf
|
47
|
+
|
48
|
+
Style/MethodDefParentheses:
|
49
|
+
EnforcedStyle: require_parentheses
|
50
|
+
|
51
|
+
Style/NumericPredicate:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
# printf style format strings are OK
|
55
|
+
Style/FormatStringToken:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
# Don't complain about using statement modifier if if blocks is 1 line long
|
59
|
+
Style/GuardClause:
|
60
|
+
Enabled: false
|
61
|
+
|
62
|
+
Layout/MultilineMethodCallIndentation:
|
63
|
+
EnforcedStyle: indented
|
64
|
+
|
65
|
+
Layout/LeadingCommentSpace:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
Style/MultilineTernaryOperator:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
Layout/SpaceBeforeComment:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
# Don't warn about the use of global regex variables $1, $2, etc...
|
75
|
+
Style/PerlBackrefs:
|
76
|
+
Enabled: false
|
77
|
+
|
78
|
+
# Don't warn about the use of global variables $:, $/, etc...
|
79
|
+
Style/SpecialGlobalVars:
|
80
|
+
Enabled: false
|
81
|
+
|
82
|
+
Style/StringLiterals:
|
83
|
+
EnforcedStyle: double_quotes
|
84
|
+
|
85
|
+
Style/FrozenStringLiteralComment:
|
86
|
+
Enabled: false
|
87
|
+
|
88
|
+
Style/PercentLiteralDelimiters:
|
89
|
+
PreferredDelimiters:
|
90
|
+
"%w": "()"
|
91
|
+
"%i": "()"
|
92
|
+
|
93
|
+
Lint/AmbiguousBlockAssociation:
|
94
|
+
Exclude:
|
95
|
+
- "spec/**/*"
|
96
|
+
|
97
|
+
Style/DateTime:
|
98
|
+
Enabled: true
|
99
|
+
|
100
|
+
Layout/ClassStructure:
|
101
|
+
Enabled: true
|
102
|
+
Categories:
|
103
|
+
module_inclusion:
|
104
|
+
ExpectedOrder:
|
105
|
+
- include
|
106
|
+
- prepend
|
107
|
+
- extend
|
108
|
+
|
109
|
+
- constants
|
110
|
+
|
111
|
+
- attribute
|
112
|
+
- attr_reader
|
113
|
+
- attr_writer
|
114
|
+
- attr_accessor
|
115
|
+
|
116
|
+
- class_methods
|
117
|
+
- public_methods
|
118
|
+
- protected_methods
|
119
|
+
- private_methods
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
zspec (1.0.0)
|
5
|
+
redis
|
6
|
+
thor
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.4.0)
|
12
|
+
coderay (1.1.2)
|
13
|
+
diff-lcs (1.3)
|
14
|
+
jaro_winkler (1.5.4)
|
15
|
+
method_source (0.9.2)
|
16
|
+
parallel (1.19.1)
|
17
|
+
parser (2.6.5.0)
|
18
|
+
ast (~> 2.4.0)
|
19
|
+
pry (0.12.2)
|
20
|
+
coderay (~> 1.1.0)
|
21
|
+
method_source (~> 0.9.0)
|
22
|
+
rainbow (3.0.0)
|
23
|
+
redis (4.1.3)
|
24
|
+
rspec (3.9.0)
|
25
|
+
rspec-core (~> 3.9.0)
|
26
|
+
rspec-expectations (~> 3.9.0)
|
27
|
+
rspec-mocks (~> 3.9.0)
|
28
|
+
rspec-core (3.9.0)
|
29
|
+
rspec-support (~> 3.9.0)
|
30
|
+
rspec-expectations (3.9.0)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.9.0)
|
33
|
+
rspec-mocks (3.9.0)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.9.0)
|
36
|
+
rspec-support (3.9.0)
|
37
|
+
rubocop (0.77.0)
|
38
|
+
jaro_winkler (~> 1.5.1)
|
39
|
+
parallel (~> 1.10)
|
40
|
+
parser (>= 2.6)
|
41
|
+
rainbow (>= 2.2.2, < 4.0)
|
42
|
+
ruby-progressbar (~> 1.7)
|
43
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
44
|
+
rubocop-rspec (1.37.0)
|
45
|
+
rubocop (>= 0.68.1)
|
46
|
+
ruby-progressbar (1.10.1)
|
47
|
+
thor (0.20.3)
|
48
|
+
unicode-display_width (1.6.0)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
bundler (~> 1.17)
|
55
|
+
pry (~> 0.12.2)
|
56
|
+
rspec (~> 3.0)
|
57
|
+
rubocop
|
58
|
+
rubocop-rspec
|
59
|
+
zspec!
|
60
|
+
|
61
|
+
BUNDLED WITH
|
62
|
+
1.17.2
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# ZSpec
|
2
|
+
|
3
|
+
ZSpec is a distributed test runner for RSpec. It consists of a `worker`, a `client`, and a `redis` store.
|
4
|
+
|
5
|
+
# The Worker
|
6
|
+
The workers are se pods running on k8s, they run `zspec work` which polls redis for work and upload the results back to redis.
|
7
|
+
|
8
|
+
# The Client
|
9
|
+
|
10
|
+
The client (in this case drone) queues up the specs by running `zspec queue_specs spec/ scenarios`. Then zspec kicks off the following events:
|
11
|
+
1) calls out to rspec to get the specs to run.
|
12
|
+
2) cleans the filepaths.
|
13
|
+
3) orders the specs by previous runtime, longest to shortest.
|
14
|
+
4) adds the specs to the redis queue.
|
15
|
+
5) sets a counter with the count of specs that were added.
|
16
|
+
|
17
|
+
Then the client runs `zspec present` which polls redis for completed specs, for each non-duplicate completed spec, it stores the result in memory and decrements the counter. Once the counter hits 0 it exits the loop and prints the results.
|
18
|
+
|
19
|
+
![workflow](https://github.com/StreetEasy/zspec/blob/master/workflow.png "Workflow")
|
20
|
+
|
21
|
+
# Having an Issue?
|
22
|
+
|
23
|
+
Issue: My ZSpec build is stuck in the images state for more than 30 minutes.
|
24
|
+
|
25
|
+
Remediation:
|
26
|
+
1) Click the Cancel button on the build in Drone
|
27
|
+
2) Click the Restart button on the build in Drone
|
28
|
+
|
29
|
+
# FAQ
|
30
|
+
|
31
|
+
1) Drone provides an output of frequent flaky specs after each test run. How do I reproduce a flaky spec identified in that report?
|
32
|
+
|
33
|
+
The unit of work in ZSpec is an individual spec file. Based on ZSpecs architecture, each spec file is run in isolation and not subject to possible polluted data from other files. To reproduce a spec, run the file mentioned in the report with rspec in your local development environment.
|
data/bin/zspec
ADDED
data/docker-compose.yaml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
version: "3"
|
2
|
+
services:
|
3
|
+
redis:
|
4
|
+
image: redis
|
5
|
+
expose:
|
6
|
+
- 6379
|
7
|
+
volumes:
|
8
|
+
- redis:/data
|
9
|
+
|
10
|
+
client:
|
11
|
+
image: ruby:2.6
|
12
|
+
command: ./hack/client/entrypoint.sh
|
13
|
+
volumes:
|
14
|
+
- ./:/app
|
15
|
+
- bundle:/usr/local/bundle
|
16
|
+
depends_on:
|
17
|
+
- redis
|
18
|
+
working_dir: /app
|
19
|
+
environment:
|
20
|
+
ZSPEC_REDIS_HOST: redis
|
21
|
+
ZSPEC_REDIS_PORT: 6379
|
22
|
+
ZSPEC_BUILD_NUMBER: '1'
|
23
|
+
|
24
|
+
worker:
|
25
|
+
image: ruby:2.6
|
26
|
+
command: ./hack/worker/entrypoint.sh
|
27
|
+
volumes:
|
28
|
+
- ./:/app
|
29
|
+
- bundle:/usr/local/bundle
|
30
|
+
depends_on:
|
31
|
+
- redis
|
32
|
+
- client
|
33
|
+
working_dir: /app
|
34
|
+
environment:
|
35
|
+
ZSPEC_REDIS_HOST: redis
|
36
|
+
ZSPEC_REDIS_PORT: 6379
|
37
|
+
ZSPEC_BUILD_NUMBER: '1'
|
38
|
+
|
39
|
+
volumes:
|
40
|
+
bundle:
|
41
|
+
redis:
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
while ! bundle exec zspec connected; do
|
3
|
+
echo "REDIS is unavailable - sleeping"
|
4
|
+
sleep 1
|
5
|
+
done
|
6
|
+
|
7
|
+
echo "Build Number: ${ZSPEC_BUILD_NUMBER}"
|
8
|
+
|
9
|
+
echo "Queuing specs"
|
10
|
+
bundle exec zspec queue_specs spec/zspec
|
11
|
+
|
12
|
+
echo "Printing results"
|
13
|
+
bundle exec zspec present
|
data/lib/zspec.rb
ADDED
data/lib/zspec/cli.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "zspec"
|
3
|
+
|
4
|
+
module ZSpec
|
5
|
+
class CLI < Thor
|
6
|
+
desc "queue_specs", ""
|
7
|
+
def queue_specs(*args)
|
8
|
+
scheduler.schedule(args)
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "present", ""
|
12
|
+
def present
|
13
|
+
failed = presenter.poll_results
|
14
|
+
queue.cleanup
|
15
|
+
tracker.cleanup
|
16
|
+
exit(1) if failed
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "work", ""
|
20
|
+
def work
|
21
|
+
worker.work
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "connected", ""
|
25
|
+
def connected
|
26
|
+
redis.connected?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def presenter
|
32
|
+
@presenter ||= ZSpec::Presenter.new(
|
33
|
+
queue: queue,
|
34
|
+
tracker: tracker,
|
35
|
+
display_count: presenter_display_count,
|
36
|
+
truncate_length: presenter_truncate_length
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def worker
|
41
|
+
@worker ||= ZSpec::Worker.new(
|
42
|
+
queue: queue,
|
43
|
+
tracker: tracker
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def queue
|
48
|
+
@queue ||= ZSpec::Queue.new(
|
49
|
+
sink: redis,
|
50
|
+
build_prefix: build_prefix,
|
51
|
+
timeout: queue_timeout,
|
52
|
+
retries: queue_retries
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def tracker
|
57
|
+
@tracker ||= ZSpec::Tracker.new(
|
58
|
+
build_prefix: build_prefix,
|
59
|
+
threshold: tracker_threshold,
|
60
|
+
hostname: hostname,
|
61
|
+
sink: redis
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def hostname
|
66
|
+
ENV["HOSTNAME"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def scheduler
|
70
|
+
@scheduler ||= ZSpec::Scheduler.new(queue: queue, tracker: tracker)
|
71
|
+
end
|
72
|
+
|
73
|
+
def redis
|
74
|
+
@redis ||= Redis.new(host: redis_host, port: redis_port)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_prefix
|
78
|
+
"#{build_number}:queue"
|
79
|
+
end
|
80
|
+
|
81
|
+
def redis_host
|
82
|
+
ENV["ZSPEC_REDIS_HOST"]
|
83
|
+
end
|
84
|
+
|
85
|
+
def redis_port
|
86
|
+
ENV["ZSPEC_REDIS_PORT"]
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_number
|
90
|
+
ENV["ZSPEC_BUILD_NUMBER"]
|
91
|
+
end
|
92
|
+
|
93
|
+
def queue_timeout
|
94
|
+
ENV["ZSPEC_QUEUE_TIMEOUT"] || 420
|
95
|
+
end
|
96
|
+
|
97
|
+
def queue_retries
|
98
|
+
ENV["ZSPEC_QUEUE_RETRIES"] || 0
|
99
|
+
end
|
100
|
+
|
101
|
+
def presenter_display_count
|
102
|
+
ENV["ZSPEC_PRESENTER_DISPLAY_COUNT"] || 25
|
103
|
+
end
|
104
|
+
|
105
|
+
def presenter_truncate_length
|
106
|
+
ENV["ZSPEC_PRESENTER_TRUNCATE_LENGTH"] || 2_000
|
107
|
+
end
|
108
|
+
|
109
|
+
def tracker_threshold
|
110
|
+
ENV["ZSPEC_TRACKER_THRESHOLD"] || 60 * 60 * 24 * 14
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|