cucumber-messages 15.0.0 → 17.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,17 +1,10 @@
1
- require 'cucumber/messages.pb'
2
- require 'cucumber/messages/binary_to_message_enumerator'
3
1
  require 'cucumber/messages/ndjson_to_message_enumerator'
4
- require 'cucumber/messages/protobuf_delimited'
5
- require 'cucumber/messages/protobuf_ndjson'
6
2
  require 'cucumber/messages/time_conversion'
7
3
  require 'cucumber/messages/id_generator'
8
-
9
- Cucumber::Messages::Envelope.include(Cucumber::Messages::WriteNdjson)
10
- Cucumber::Messages::Envelope.include(Cucumber::Messages::WriteDelimited)
11
- Cucumber::Messages::Envelope.extend(Cucumber::Messages::ParseDelimited)
4
+ require 'cucumber/messages.deserializers'
12
5
 
13
6
  module Cucumber
14
7
  module Messages
15
8
  VERSION = File.read(File.expand_path("../../VERSION", __dir__)).strip
16
9
  end
17
- end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'cucumber/messages/message/deserialization'
2
+ require 'cucumber/messages/message/serialization'
3
+
4
+ module Cucumber
5
+ module Messages
6
+ class Message
7
+ include Cucumber::Messages::Message::Deserialization
8
+ include Cucumber::Messages::Message::Serialization
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ require 'cucumber/messages/message/utils'
2
+ require 'json'
3
+
4
+ module Cucumber
5
+ module Messages
6
+ class Message
7
+ include Cucumber::Messages::Message::Utils
8
+
9
+ module Deserialization
10
+ def self.included(other)
11
+ other.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ ##
17
+ # Returns a new Message - or messages into an array - deserialized from the given json document.
18
+ # CamelCased keys are properly converted to snake_cased attributes in the process
19
+ #
20
+ # Cucumber::Messages::Duration.from_json('{"seconds":1,"nanos":42}') # => #<Cucumber::Messages::Duration:0x00007efda134c290 @seconds=1, @nanos=42>
21
+ # Cucumber::Messages::PickleTag.from_json('{"name":"foo","astNodeId":"abc-def"}') # => #<Cucumber::Messages::PickleTag:0x00007efda138cdb8 @name="foo", @ast_node_id="abc-def">
22
+ #
23
+ # It is recursive so embedded messages are also processed.
24
+ #
25
+ # json_string = { location: { line: 2 }, text: "comment" }.to_json
26
+ # Cucumber::Messages::Comment.from_json(json_string) # => #<Cucumber::Messages::Comment:0x00007efda6abf888 @location=#<Cucumber::Messages::Location:0x00007efda6abf978 @line=2, @column=nil>, @text="comment">
27
+ #
28
+ # json_string = { uri: 'file:///...', comments: [{text: 'text comment'}, {text: 'another comment'}]}.to_json
29
+ # Cucumber::Messages::GherkinDocument.from_json(json_string) # => #<Cucumber::Messages::GherkinDocument:0x00007efda11e6a90 ... @comments=[#<Cucumber::Messages::Comment:0x00007efda11e6e50 ..., #<Cucumber::Messages::Comment:0x00007efda11e6b58 ...>]>
30
+ #
31
+
32
+ def from_json(json_string)
33
+ from_h(JSON.parse(json_string, { symbolize_names: true }))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,73 @@
1
+ require 'cucumber/messages/message/utils'
2
+ require 'json'
3
+
4
+ module Cucumber
5
+ module Messages
6
+ class Message
7
+ include Cucumber::Messages::Message::Utils
8
+
9
+ module Serialization
10
+
11
+ ##
12
+ # Returns a new Hash formed from the message attributes
13
+ # If +camelize:+ keyword parameter is set to true, then keys will be camelized
14
+ # If +reject_nil_values:+ keyword parameter is set to true, resulting hash won't include nil values
15
+ #
16
+ # Cucumber::Messages::Duration.new(seconds: 1, nanos: 42).to_h # => { seconds: 1, nanos: 42 }
17
+ # Cucumber::Messages::PickleTag.new(name: 'foo', ast_node_id: 'abc-def').to_h(camelize: true) # => { name: 'foo', astNodeId: 'abc-def' }
18
+ # Cucumber::Messages::PickleTag.new(name: 'foo', ast_node_id: nil).to_h(reject_nil_values: true) # => { name: 'foo' }
19
+ #
20
+ # It is recursive so embedded messages are also processed
21
+ #
22
+ # location = Cucumber::Messages::Location.new(line: 2)
23
+ # Cucumber::Messages::Comment.new(location: location, text: 'comment').to_h # => { location: { line: 2, :column: nil }, text: "comment" }
24
+ #
25
+
26
+ def to_h(camelize: false, reject_nil_values: false)
27
+ resulting_hash = self.instance_variables.map do |variable_name|
28
+ h_key = variable_name[1..-1]
29
+ h_key = Cucumber::Messages::Message.camelize(h_key) if camelize
30
+
31
+ h_value = prepare_value(
32
+ self.instance_variable_get(variable_name),
33
+ camelize: camelize,
34
+ reject_nil_values: reject_nil_values
35
+ )
36
+
37
+ [ h_key.to_sym, h_value ]
38
+ end.to_h
39
+
40
+ resulting_hash.reject! { |_, value| value.nil? } if reject_nil_values
41
+ resulting_hash
42
+ end
43
+
44
+ ##
45
+ # Generates a JSON document from the message.
46
+ # Keys are camelized during the process. Null values are not part of the json document.
47
+ #
48
+ # Cucumber::Messages::Duration.new(seconds: 1, nanos: 42).to_json # => '{"seconds":1,"nanos":42}'
49
+ # Cucumber::Messages::PickleTag.new(name: 'foo', ast_node_id: 'abc-def').to_json # => '{"name":"foo","astNodeId":"abc-def"}'
50
+ # Cucumber::Messages::PickleTag.new(name: 'foo', ast_node_id: nil).to_json # => '{"name":"foo"}'
51
+ #
52
+ # As #to_h, the method is recursive
53
+ #
54
+ # location = Cucumber::Messages::Location.new(line: 2)
55
+ # Cucumber::Messages::Comment.new(location: location, text: 'comment').to_json # => '{"location":{"line":2,"column":null},"text":"comment"}'
56
+ #
57
+
58
+ def to_json
59
+ to_h(camelize: true, reject_nil_values: true).to_json
60
+ end
61
+
62
+ private
63
+
64
+ def prepare_value(value, camelize:, reject_nil_values:)
65
+ return value.to_h(camelize: camelize, reject_nil_values: reject_nil_values) if value.is_a?(Cucumber::Messages::Message)
66
+ return value.map { |v| prepare_value(v, camelize: camelize, reject_nil_values: reject_nil_values) } if value.is_a?(Array)
67
+
68
+ value
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,46 @@
1
+ module Cucumber
2
+ module Messages
3
+ class Message
4
+ module Utils
5
+ def self.included(other)
6
+ other.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ ##
12
+ # Makes an underscored, lowercase form from the expression in the string.
13
+ #
14
+ # underscore('GherkinDocument') # => "gherkin_document"
15
+ #
16
+ # This is a simplified version of the Ruby on Rails implementation
17
+ # https://github.com/rails/rails/blob/v6.1.3.2/activesupport/lib/active_support/inflector/methods.rb#L92
18
+
19
+ def underscore(term)
20
+ return term unless /[A-Z-]/.match?(term)
21
+
22
+ word = term.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
23
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
24
+ word.tr!("-", "_")
25
+ word.downcase!
26
+ word
27
+ end
28
+
29
+ ##
30
+ # Converts strings to UpperCamelCase.
31
+ #
32
+ # camelize('gherkin_document') # => "GherkinDocument"
33
+ #
34
+ # This is a simplified version of the Ruby on Rails implementation
35
+ # https://github.com/rails/rails/blob/v6.1.3.2/activesupport/lib/active_support/inflector/methods.rb#L69
36
+
37
+ def camelize(term)
38
+ camelized = term.to_s
39
+ camelized.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
40
+ camelized
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,4 +1,4 @@
1
- require 'cucumber/messages/varint'
1
+ require 'cucumber/messages.deserializers'
2
2
 
3
3
  module Cucumber
4
4
  module Messages
@@ -8,7 +8,7 @@ module Cucumber
8
8
  io.each_line do |line|
9
9
  next if line.strip.empty?
10
10
  begin
11
- m = Cucumber::Messages::Envelope.from_json(line)
11
+ m = Envelope.from_json(line)
12
12
  rescue => e
13
13
  raise "Not JSON: #{line.strip}"
14
14
  end
@@ -4,25 +4,28 @@ module Cucumber
4
4
  NANOSECONDS_PER_SECOND = 1000000000
5
5
 
6
6
  def time_to_timestamp(time)
7
- Timestamp.new(
8
- seconds: time.to_i,
9
- nanos: time.nsec
10
- )
7
+ {
8
+ 'seconds' => time.to_i,
9
+ 'nanos' => time.nsec
10
+ }
11
11
  end
12
12
 
13
13
  def timestamp_to_time(timestamp)
14
- Time.at(timestamp.seconds + timestamp.nanos.to_f / NANOSECONDS_PER_SECOND)
14
+ Time.at(timestamp['seconds'] + timestamp['nanos'].to_f / NANOSECONDS_PER_SECOND)
15
15
  end
16
16
 
17
17
  def seconds_to_duration(seconds_float)
18
18
  seconds, second_modulus = seconds_float.divmod(1)
19
19
  nanos = second_modulus * NANOSECONDS_PER_SECOND
20
- Duration.new(seconds: seconds, nanos: nanos)
20
+ {
21
+ 'seconds' => seconds,
22
+ 'nanos' => nanos.to_i
23
+ }
21
24
  end
22
25
 
23
26
  def duration_to_seconds(duration)
24
- seconds_part = duration.seconds
25
- nanos_part = duration.nanos.to_f / NANOSECONDS_PER_SECOND
27
+ seconds_part = duration['seconds']
28
+ nanos_part = duration['nanos'].to_f / NANOSECONDS_PER_SECOND
26
29
  seconds_part + nanos_part
27
30
  end
28
31
  end
@@ -0,0 +1,27 @@
1
+ require 'json'
2
+ require 'cucumber/messages'
3
+
4
+ module Cucumber
5
+ module Messages
6
+ describe 'messages acdeptance tests' do
7
+ # TODO: Remove '/minimal' from the glob
8
+ Dir["#{File.dirname(__FILE__)}/../../../../../compatibility-kit/javascript/features/**/*.ndjson"].each do |ndjson_file|
9
+ it "deserialises and serialises messages in #{ndjson_file}" do
10
+ File.open(ndjson_file, 'r:utf-8') do |io|
11
+ io.each_line do |json|
12
+ check(json)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def check(json)
19
+ hash = JSON.parse(json)
20
+ envelope = Envelope.from_json(json)
21
+ new_json = envelope.to_json
22
+ new_hash = JSON.parse(new_json)
23
+ expect(new_hash).to eq(hash)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'rspec'
2
+ require 'cucumber/messages/message/dummy_messages'
3
+
4
+ describe Cucumber::Messages::Message::Deserialization do
5
+ describe '#from_json' do
6
+ subject { Cucumber::Messages::Message.from_json(json_document) }
7
+
8
+ let(:json_document) { '' }
9
+
10
+ context 'with a valid JSON document' do
11
+ let(:json_document) { '{"simpleMessage":{"isString":"answer"}}' }
12
+
13
+ it 'deserialize the message using #from_h' do
14
+ allow(Cucumber::Messages::Message).to receive(:from_h)
15
+
16
+ subject
17
+
18
+ expect(Cucumber::Messages::Message)
19
+ .to have_received(:from_h)
20
+ .with({ simpleMessage: { isString: "answer" } })
21
+ end
22
+ end
23
+
24
+ context 'with an invalid JSON document' do
25
+ let(:json_document) { '{foo: bar}' }
26
+
27
+ it { expect { subject }.to raise_error(JSON::ParserError) }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ require 'cucumber/messages/message'
2
+
3
+ module Cucumber::Messages
4
+ class SimpleMessage < Message
5
+ attr_reader :is_nil, :is_string, :is_array, :is_number
6
+
7
+ def initialize(is_nil: nil, is_string: '', is_array: [], is_number: 0)
8
+ @is_nil = is_nil
9
+ @is_string = is_string
10
+ @is_array = is_array
11
+ @is_number = is_number
12
+ end
13
+ end
14
+
15
+ class EnumMessage
16
+ ENUM = 'an enum'
17
+ end
18
+
19
+ class ComprehensiveMessage < Message
20
+ attr_reader :simple_message, :message_array, :is_enum
21
+
22
+ def initialize(
23
+ simple_message: SimpleMessage.new,
24
+ message_array: [SimpleMessage.new, SimpleMessage.new],
25
+ is_enum: EnumMessage::ENUM
26
+ )
27
+ @simple_message = simple_message
28
+ @message_array = message_array
29
+ @is_enum = is_enum
30
+ end
31
+
32
+ private
33
+
34
+ def self.message_array_from_h(hash)
35
+ SimpleMessage.from_h(hash)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,89 @@
1
+ require 'rspec'
2
+ require 'cucumber/messages/message/dummy_messages'
3
+
4
+ describe Cucumber::Messages::Message::Serialization do
5
+ context 'with a simple message' do
6
+ describe '#to_h' do
7
+ subject { Cucumber::Messages::SimpleMessage.new.to_h }
8
+
9
+ it 'returns a Hash with all the attributes of the Message' do
10
+ expect(subject).to eq({ is_nil: nil, is_string: '', is_array: [], is_number: 0 })
11
+ end
12
+
13
+ context 'whith camelize: true' do
14
+ subject { Cucumber::Messages::SimpleMessage.new.to_h(camelize: true) }
15
+
16
+ it 'camelize the keys of the resulting hash' do
17
+ expect(subject).to eq({ isNil: nil, isString: '', isArray: [], isNumber: 0 })
18
+ end
19
+ end
20
+
21
+ context 'with reject_nil_values: true' do
22
+ subject { Cucumber::Messages::SimpleMessage.new.to_h(reject_nil_values: true) }
23
+
24
+ it 'rejects nil values from the resulting hash' do
25
+ expect(subject).to eq({ is_string: '', is_array: [], is_number: 0 })
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '#to_json' do
31
+ subject { Cucumber::Messages::SimpleMessage.new.to_json }
32
+
33
+ it 'returns a JSON document with all the attributes of the Message except nil values' do
34
+ expect(subject).to eq({ isString: '', isArray: [], isNumber: 0 }.to_json)
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'with a message which embeds other messages' do
40
+ describe '#to_h' do
41
+ subject { Cucumber::Messages::ComprehensiveMessage.new.to_h }
42
+
43
+ it 'includes hash representation of embedded messages' do
44
+ expect(subject[:simple_message]).to eq({ is_nil: nil, is_string: '', is_array: [], is_number: 0 })
45
+ expect(subject[:is_enum]).to eq 'an enum'
46
+ end
47
+
48
+ it 'includes hash representation of messages arrays' do
49
+ expect(subject[:message_array]).to eq([
50
+ { is_nil: nil, is_string: '', is_array: [], is_number: 0 },
51
+ { is_nil: nil, is_string: '', is_array: [], is_number: 0 }
52
+ ])
53
+ end
54
+
55
+ context 'whith camelize: true' do
56
+ subject { Cucumber::Messages::ComprehensiveMessage.new.to_h(camelize: true) }
57
+
58
+ it 'camelize the keys of the embedded messages resulting hashes' do
59
+ expect(subject[:simpleMessage]).to eq({ isNil: nil, isString: '', isArray: [], isNumber: 0 })
60
+ end
61
+
62
+ it 'camelize the keys of hashes for messages arrays' do
63
+ expect(subject[:messageArray]).to eq([
64
+ { isNil: nil, isString: '', isArray: [], isNumber: 0 },
65
+ { isNil: nil, isString: '', isArray: [], isNumber: 0 }
66
+ ])
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#to_json' do
72
+ subject { Cucumber::Messages::ComprehensiveMessage.new.to_json }
73
+
74
+ it 'returns a JSON document with embedded messages' do
75
+ expect(subject).to include({ isString: '', isArray: [], isNumber: 0}.to_json)
76
+ expect(subject).to include('"isEnum":"an enum"')
77
+ end
78
+
79
+ it 'returns a JSON document with messages arrays' do
80
+ expect(subject).to include(
81
+ [
82
+ { isString: '', isArray: [], isNumber: 0},
83
+ { isString: '', isArray: [], isNumber: 0}
84
+ ].to_json
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,30 @@
1
+ require 'rspec'
2
+ require 'cucumber/messages/message/utils'
3
+
4
+ class DummyTestClass
5
+ include Cucumber::Messages::Message::Utils
6
+ end
7
+
8
+ describe Cucumber::Messages::Message::Utils do
9
+ subject { DummyTestClass }
10
+
11
+ describe '#underscore' do
12
+ it { expect(subject.underscore('test')).to eq 'test' }
13
+ it { expect(subject.underscore('testTest')).to eq 'test_test' }
14
+ it { expect(subject.underscore('')).to eq '' }
15
+ it { expect(subject.underscore('T')).to eq 't' }
16
+ it { expect(subject.underscore('test123test456Test')).to eq 'test123test456_test' }
17
+ it { expect(subject.underscore('test-test')).to eq 'test_test' }
18
+ it { expect(subject.underscore('TEST_Test')).to eq 'test_test' }
19
+ it { expect(subject.underscore('test-Test')).to eq 'test_test' }
20
+ end
21
+
22
+ describe '#camelize' do
23
+ it { expect(subject.camelize('test')).to eq 'test' }
24
+ it { expect(subject.camelize('test_test')).to eq 'testTest' }
25
+ it { expect(subject.camelize('Test_TeSt')).to eq 'TestTest' }
26
+ it { expect(subject.camelize('')).to eq '' }
27
+ it { expect(subject.camelize('test123test4_5_6_test')).to eq 'test123test456Test' }
28
+ it { expect(subject.camelize('test-test')).to eq 'test-test' }
29
+ end
30
+ end