zspec 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1 @@
1
+
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -fd -c
@@ -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
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in zspec.gemspec
6
+ gemspec
@@ -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
@@ -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.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.expand_path("../lib", __dir__)
3
+ $:.unshift lib unless $:.include? lib
4
+
5
+ require "zspec/cli"
6
+
7
+ ZSpec::CLI.start
@@ -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
@@ -0,0 +1,10 @@
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 "Starting worker"
10
+ bundle exec zspec work
@@ -0,0 +1,9 @@
1
+ require "rspec/core"
2
+ require "redis"
3
+ require "json"
4
+
5
+ module ZSpec
6
+ EXPIRE_SECONDS = 1800
7
+
8
+ Dir[File.join(__dir__, "zspec", "*.rb")].each { |file| require file }
9
+ end
@@ -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