wsdirector-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +30 -0
- data/README.md +22 -6
- data/lib/wsdirector-cli.rb +3 -0
- data/lib/wsdirector.rb +1 -0
- data/lib/wsdirector/client.rb +3 -1
- data/lib/wsdirector/protocols.rb +2 -0
- data/lib/wsdirector/protocols/action_cable.rb +14 -0
- data/lib/wsdirector/protocols/base.rb +68 -3
- data/lib/wsdirector/scenario_reader.rb +2 -10
- data/lib/wsdirector/utils.rb +15 -0
- data/lib/wsdirector/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2baebc5541a7fa47137932138d2605c7d7c7666542ef1967a056b884660d06df
|
4
|
+
data.tar.gz: b1179e52ccc50773d88fae780ce58d6a7038b45ad3ae29ac52427f594301de05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79a5b74d539c88e58932f67e91730652a13f3051d753cd14539c5b55a8a410134e9b16bca26debff21aa51db7943a0263bb859b1ff68c0444d2e7ba0d617312c
|
7
|
+
data.tar.gz: 505976c437539b628b9325a0f3b7f423e55c4df33f29fc00476aaf2bbaf9f5e786cb6dc5bca9fb1cb10e6aca92dec9265e6a8fbfde704ecfc5be2734e6b4ec73
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,35 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## master
|
4
|
+
|
5
|
+
## 0.3.0 (2018-05-02)
|
6
|
+
|
7
|
+
- Add `debug` action and options. ([@palkan][])
|
8
|
+
|
9
|
+
Allows to print arbitrary messages during the execution.
|
10
|
+
|
11
|
+
- Add `sleep` action. ([@palkan][])
|
12
|
+
|
13
|
+
- Add `receive_all` action. ([@palkan][])
|
14
|
+
|
15
|
+
Allows to handle multiple messages with unspecified order:
|
16
|
+
|
17
|
+
```yml
|
18
|
+
- receive_all:
|
19
|
+
messages:
|
20
|
+
- data:
|
21
|
+
text: "Hello!"
|
22
|
+
multiplier: ":scale + :scale"
|
23
|
+
channel: "chat"
|
24
|
+
params:
|
25
|
+
id: 2
|
26
|
+
- data:
|
27
|
+
text: "message sent"
|
28
|
+
channel: "chat"
|
29
|
+
params:
|
30
|
+
id: 2
|
31
|
+
```
|
32
|
+
|
3
33
|
## 0.2.0 (2017-11-05)
|
4
34
|
|
5
35
|
- Initial version. ([@palkan][], [@Kirillvs][], [@Grandman][])
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
[](http://cultofmartians.com/tasks/websocket-director.html)
|
2
|
+
[](https://rubygems.org/gems/wsdirector-cli) [](https://travis-ci.org/palkan/wsdirector) [](https://circleci.com/gh/palkan/wsdirector)
|
2
3
|
|
3
4
|
# WebSocket Director
|
4
5
|
|
@@ -6,9 +7,6 @@ Command line tool for testing websocket servers using scenarios.
|
|
6
7
|
|
7
8
|
Suitable for testing any websocket server implementation, like [Action Cable](https://github.com/rails/rails/tree/master/actioncable), [Websocket Eventmachine Server](https://github.com/imanel/websocket-eventmachine-server), [Litecable](https://github.com/palkan/litecable) and so on.
|
8
9
|
|
9
|
-
<a href="https://evilmartians.com/">
|
10
|
-
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
11
|
-
|
12
10
|
## Installation
|
13
11
|
|
14
12
|
```bash
|
@@ -17,7 +15,7 @@ Suitable for testing any websocket server implementation, like [Action Cable](ht
|
|
17
15
|
|
18
16
|
## Usage
|
19
17
|
|
20
|
-
Create YAML file with
|
18
|
+
Create YAML file with simple testing script:
|
21
19
|
|
22
20
|
```yml
|
23
21
|
# script.yml
|
@@ -43,7 +41,7 @@ You can create more complex scenarios with multiple client groups:
|
|
43
41
|
- client: # first clients group
|
44
42
|
name: "publisher" # optional group name
|
45
43
|
multiplier: ":scale" # :scale take number from -s param, and run :scale number of clients in this group
|
46
|
-
actions:
|
44
|
+
actions:
|
47
45
|
- receive:
|
48
46
|
data: "Welcome"
|
49
47
|
- wait_all # makes all clients in all groups wait untill every client get this point (global barrier)
|
@@ -133,6 +131,24 @@ Scenario:
|
|
133
131
|
text: "hello"
|
134
132
|
```
|
135
133
|
|
134
|
+
## Future Ideas
|
135
|
+
|
136
|
+
- Report timings (per-client and aggregates)
|
137
|
+
|
138
|
+
- File-less scenarios (JSON-encoded?), e.g.
|
139
|
+
|
140
|
+
```shell
|
141
|
+
wsdirector -i '{"receive": "hello"}' localhost:9898/ws
|
142
|
+
```
|
143
|
+
|
144
|
+
- Connection parameters (headers, query params, etc)
|
145
|
+
|
146
|
+
- Testing frameworks integrations
|
147
|
+
|
148
|
+
- Loading protocols dynamically
|
149
|
+
|
150
|
+
- What else? [Submit an issue!](https://github.com/palkan/wsdirector/issues/new)
|
151
|
+
|
136
152
|
## Contributing
|
137
153
|
|
138
154
|
Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/wsdirector.
|
data/lib/wsdirector.rb
CHANGED
data/lib/wsdirector/client.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "websocket-client-simple"
|
4
|
+
require "securerandom"
|
4
5
|
|
5
6
|
module WSDirector
|
6
7
|
# WebSocket client
|
7
8
|
class Client
|
8
9
|
WAIT_WHEN_EXPECTING_EVENT = 5
|
9
10
|
|
10
|
-
attr_reader :ws
|
11
|
+
attr_reader :ws, :id
|
11
12
|
|
12
13
|
# Create new WebSocket client and connect to WSDirector
|
13
14
|
# ws URL.
|
@@ -22,6 +23,7 @@ module WSDirector
|
|
22
23
|
open = Concurrent::Promise.new
|
23
24
|
client = self
|
24
25
|
|
26
|
+
@id = SecureRandom.hex(6)
|
25
27
|
@ws = WebSocket::Client::Simple.connect(path) do |ws|
|
26
28
|
ws.on(:open) do |_event|
|
27
29
|
open.set(true)
|
data/lib/wsdirector/protocols.rb
CHANGED
@@ -12,6 +12,8 @@ module WSDirector
|
|
12
12
|
module Protocols # :nodoc:
|
13
13
|
# Raised when received not expected message
|
14
14
|
class UnmatchedExpectationError < WSDirector::Error; end
|
15
|
+
# Raised when received message is unexpected
|
16
|
+
class UnexpectedMessageError < WSDirector::Error; end
|
15
17
|
# Raised when nothing has been received
|
16
18
|
class NoMessageError < WSDirector::Error; end
|
17
19
|
|
@@ -50,6 +50,20 @@ module WSDirector
|
|
50
50
|
super("data" => { "identifier" => identifier, "message" => message })
|
51
51
|
end
|
52
52
|
|
53
|
+
def receive_all(step)
|
54
|
+
messages = step["messages"]
|
55
|
+
|
56
|
+
return super if messages.nil? || messages.empty?
|
57
|
+
|
58
|
+
messages.each do |msg|
|
59
|
+
next unless msg.key?("channel")
|
60
|
+
identifier = extract_identifier(msg)
|
61
|
+
msg["data"] = { "identifier" => identifier, "message" => msg["data"] }
|
62
|
+
end
|
63
|
+
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
53
67
|
private
|
54
68
|
|
55
69
|
def extract_identifier(step)
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "time"
|
4
|
+
|
3
5
|
module WSDirector
|
4
6
|
module Protocols
|
5
7
|
# Base protocol describes basic actions
|
6
8
|
class Base
|
9
|
+
include WSDirector::Utils
|
10
|
+
|
7
11
|
def initialize(task)
|
8
12
|
@task = task
|
9
13
|
end
|
@@ -18,14 +22,72 @@ module WSDirector
|
|
18
22
|
public_send(type, step)
|
19
23
|
end
|
20
24
|
|
25
|
+
# Sleeps for a specified number of seconds.
|
26
|
+
#
|
27
|
+
# If "shift" is provided than the initial value is
|
28
|
+
# shifted by random number from (-shift, shift).
|
29
|
+
#
|
30
|
+
# Set "debug" to true to print the delay time.
|
31
|
+
def sleep(step)
|
32
|
+
delay = step.fetch("time").to_f
|
33
|
+
shift = step.fetch("shift", 0).to_f
|
34
|
+
|
35
|
+
delay = delay - shift * rand + shift * rand
|
36
|
+
|
37
|
+
print("Sleep for #{delay}s") if step.fetch("debug", false)
|
38
|
+
|
39
|
+
Kernel.sleep delay if delay > 0
|
40
|
+
end
|
41
|
+
|
42
|
+
# Prints provided message
|
43
|
+
def debug(step)
|
44
|
+
print(step.fetch("message"))
|
45
|
+
end
|
46
|
+
|
21
47
|
def receive(step)
|
22
48
|
expected = step.fetch("data")
|
23
49
|
received = client.receive
|
24
|
-
|
50
|
+
raise UnmatchedExpectationError, prepare_receive_error(expected, received) unless
|
51
|
+
receive_matches?(expected, received)
|
25
52
|
rescue ThreadError
|
26
53
|
raise NoMessageError, "Expected to receive #{expected} but nothing has been received"
|
27
54
|
end
|
28
55
|
|
56
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
57
|
+
def receive_all(step)
|
58
|
+
messages = step.delete("messages")
|
59
|
+
raise ArgumentError, "Messages array must be specified" if
|
60
|
+
messages.nil? || messages.empty?
|
61
|
+
|
62
|
+
expected =
|
63
|
+
Hash[messages.map do |msg|
|
64
|
+
multiplier = parse_multiplier(msg.delete("multiplier") || "1")
|
65
|
+
[msg["data"], multiplier]
|
66
|
+
end]
|
67
|
+
|
68
|
+
total_expected = expected.values.sum
|
69
|
+
total_received = 0
|
70
|
+
|
71
|
+
total_expected.times do
|
72
|
+
received = client.receive
|
73
|
+
|
74
|
+
total_received += 1
|
75
|
+
|
76
|
+
match = expected.find { |k, _| receive_matches?(k, received) }
|
77
|
+
|
78
|
+
raise UnexpectedMessageError, "Unexpected message received: #{received}" if
|
79
|
+
match.nil?
|
80
|
+
|
81
|
+
expected[match.first] -= 1
|
82
|
+
expected.delete(match.first) if expected[match.first].zero?
|
83
|
+
end
|
84
|
+
rescue ThreadError
|
85
|
+
raise NoMessageError,
|
86
|
+
"Expected to receive #{total_expected} messages " \
|
87
|
+
"but received only #{total_received}"
|
88
|
+
end
|
89
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
90
|
+
|
29
91
|
def send(step)
|
30
92
|
data = step.fetch("data")
|
31
93
|
data = JSON.generate(data) if data.is_a?(Hash)
|
@@ -51,8 +113,7 @@ module WSDirector
|
|
51
113
|
def receive_matches?(expected, received)
|
52
114
|
received = JSON.parse(received) if expected.is_a?(Hash)
|
53
115
|
|
54
|
-
|
55
|
-
received != expected
|
116
|
+
received == expected
|
56
117
|
end
|
57
118
|
|
58
119
|
def prepare_receive_error(expected, received)
|
@@ -62,6 +123,10 @@ module WSDirector
|
|
62
123
|
++ got: #{received}
|
63
124
|
MSG
|
64
125
|
end
|
126
|
+
|
127
|
+
def print(msg)
|
128
|
+
$stdout.puts "DEBUG #{Time.now.iso8601} client=#{client.id} #{msg}\n"
|
129
|
+
end
|
65
130
|
end
|
66
131
|
end
|
67
132
|
end
|
@@ -3,9 +3,9 @@
|
|
3
3
|
module WSDirector
|
4
4
|
# Read and parse YAML scenario
|
5
5
|
class ScenarioReader
|
6
|
-
MULTIPLIER_FORMAT = /^[-+*\\\d ]+$/
|
7
|
-
|
8
6
|
class << self
|
7
|
+
include WSDirector::Utils
|
8
|
+
|
9
9
|
def parse(file_path)
|
10
10
|
contents = YAML.load_file(file_path)
|
11
11
|
|
@@ -62,14 +62,6 @@ module WSDirector
|
|
62
62
|
{ "total" => total_count, "clients" => clients }
|
63
63
|
end
|
64
64
|
|
65
|
-
def parse_multiplier(str)
|
66
|
-
prepared = str.to_s.gsub(":scale", WSDirector.config.scale.to_s)
|
67
|
-
raise WSDirector::Error, "Unknown multiplier format: #{str}" unless
|
68
|
-
prepared =~ MULTIPLIER_FORMAT
|
69
|
-
|
70
|
-
eval(prepared) # rubocop:disable Security/Eval
|
71
|
-
end
|
72
|
-
|
73
65
|
def parse_ingore(str)
|
74
66
|
return unless str
|
75
67
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WSDirector
|
4
|
+
module Utils # :nodoc:
|
5
|
+
MULTIPLIER_FORMAT = /^[-+*\\\d ]+$/
|
6
|
+
|
7
|
+
def parse_multiplier(str)
|
8
|
+
prepared = str.to_s.gsub(":scale", WSDirector.config.scale.to_s)
|
9
|
+
raise WSDirector::Error, "Unknown multiplier format: #{str}" unless
|
10
|
+
prepared =~ MULTIPLIER_FORMAT
|
11
|
+
|
12
|
+
eval(prepared) # rubocop:disable Security/Eval
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/wsdirector/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wsdirector-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kirill Arkhipov
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2018-05-02 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: websocket-client-simple
|
@@ -194,6 +194,7 @@ files:
|
|
194
194
|
- LICENSE.txt
|
195
195
|
- README.md
|
196
196
|
- bin/wsdirector
|
197
|
+
- lib/wsdirector-cli.rb
|
197
198
|
- lib/wsdirector.rb
|
198
199
|
- lib/wsdirector/cli.rb
|
199
200
|
- lib/wsdirector/client.rb
|
@@ -209,6 +210,7 @@ files:
|
|
209
210
|
- lib/wsdirector/runner.rb
|
210
211
|
- lib/wsdirector/scenario_reader.rb
|
211
212
|
- lib/wsdirector/task.rb
|
213
|
+
- lib/wsdirector/utils.rb
|
212
214
|
- lib/wsdirector/version.rb
|
213
215
|
homepage: https://github.com/palkan/wsdirector
|
214
216
|
licenses:
|
@@ -222,7 +224,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
222
224
|
requirements:
|
223
225
|
- - ">="
|
224
226
|
- !ruby/object:Gem::Version
|
225
|
-
version:
|
227
|
+
version: 2.4.0
|
226
228
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
229
|
requirements:
|
228
230
|
- - ">="
|
@@ -230,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
232
|
version: '0'
|
231
233
|
requirements: []
|
232
234
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.
|
235
|
+
rubygems_version: 2.7.4
|
234
236
|
signing_key:
|
235
237
|
specification_version: 4
|
236
238
|
summary: Command line tool for testing websocket servers using scenarios.
|