cucumber-messages 15.0.0 → 17.0.1

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.
@@ -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