logstash-core 2.4.1-java → 5.0.0.alpha1-java

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.

Potentially problematic release.


This version of logstash-core might be problematic. Click here for more details.

Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/logstash-core/version.rb +1 -1
  3. data/lib/logstash/agent.rb +124 -411
  4. data/lib/logstash/api/init.ru +31 -0
  5. data/lib/logstash/api/lib/app.rb +40 -0
  6. data/lib/logstash/api/lib/app/command.rb +29 -0
  7. data/lib/logstash/api/lib/app/command_factory.rb +29 -0
  8. data/lib/logstash/api/lib/app/commands/stats/events_command.rb +13 -0
  9. data/lib/logstash/api/lib/app/commands/stats/hotthreads_command.rb +120 -0
  10. data/lib/logstash/api/lib/app/commands/stats/memory_command.rb +25 -0
  11. data/lib/logstash/api/lib/app/commands/system/basicinfo_command.rb +15 -0
  12. data/lib/logstash/api/lib/app/commands/system/plugins_command.rb +28 -0
  13. data/lib/logstash/api/lib/app/modules/node.rb +25 -0
  14. data/lib/logstash/api/lib/app/modules/node_stats.rb +51 -0
  15. data/lib/logstash/api/lib/app/modules/plugins.rb +15 -0
  16. data/lib/logstash/api/lib/app/modules/stats.rb +21 -0
  17. data/lib/logstash/api/lib/app/root.rb +13 -0
  18. data/lib/logstash/api/lib/app/service.rb +61 -0
  19. data/lib/logstash/api/lib/app/stats.rb +56 -0
  20. data/lib/logstash/api/lib/helpers/app_helpers.rb +23 -0
  21. data/lib/logstash/codecs/base.rb +1 -29
  22. data/lib/logstash/config/config_ast.rb +18 -31
  23. data/lib/logstash/config/loader.rb +3 -5
  24. data/lib/logstash/config/mixin.rb +25 -64
  25. data/lib/logstash/filter_delegator.rb +65 -0
  26. data/lib/logstash/inputs/base.rb +1 -1
  27. data/lib/logstash/inputs/metrics.rb +47 -0
  28. data/lib/logstash/instrument/collector.rb +109 -0
  29. data/lib/logstash/instrument/metric.rb +102 -0
  30. data/lib/logstash/instrument/metric_store.rb +228 -0
  31. data/lib/logstash/instrument/metric_type.rb +24 -0
  32. data/lib/logstash/instrument/metric_type/base.rb +35 -0
  33. data/lib/logstash/instrument/metric_type/counter.rb +29 -0
  34. data/lib/logstash/instrument/metric_type/gauge.rb +22 -0
  35. data/lib/logstash/instrument/metric_type/mean.rb +33 -0
  36. data/lib/logstash/instrument/namespaced_metric.rb +54 -0
  37. data/lib/logstash/instrument/null_metric.rb +4 -3
  38. data/lib/logstash/instrument/periodic_poller/base.rb +57 -0
  39. data/lib/logstash/instrument/periodic_poller/jvm.rb +92 -0
  40. data/lib/logstash/instrument/periodic_poller/os.rb +13 -0
  41. data/lib/logstash/instrument/periodic_poller/periodic_poller_observer.rb +19 -0
  42. data/lib/logstash/instrument/periodic_pollers.rb +26 -0
  43. data/lib/logstash/instrument/snapshot.rb +16 -0
  44. data/lib/logstash/json.rb +2 -3
  45. data/lib/logstash/namespace.rb +1 -0
  46. data/lib/logstash/output_delegator.rb +16 -3
  47. data/lib/logstash/outputs/base.rb +1 -32
  48. data/lib/logstash/pipeline.rb +67 -8
  49. data/lib/logstash/plugin.rb +57 -19
  50. data/lib/logstash/runner.rb +348 -84
  51. data/lib/logstash/util.rb +9 -0
  52. data/lib/logstash/util/duration_formatter.rb +15 -0
  53. data/lib/logstash/util/java_version.rb +2 -4
  54. data/lib/logstash/util/loggable.rb +29 -0
  55. data/lib/logstash/version.rb +1 -1
  56. data/lib/logstash/webserver.rb +98 -0
  57. data/locales/en.yml +42 -24
  58. data/logstash-core.gemspec +9 -6
  59. data/spec/api/lib/api/node_spec.rb +64 -0
  60. data/spec/api/lib/api/node_stats_spec.rb +68 -0
  61. data/spec/api/lib/api/plugins_spec.rb +57 -0
  62. data/spec/api/lib/api/root_spec.rb +20 -0
  63. data/spec/api/lib/api/stats_spec.rb +19 -0
  64. data/spec/api/lib/commands/events_spec.rb +17 -0
  65. data/spec/api/lib/commands/jvm_spec.rb +45 -0
  66. data/spec/api/spec_helper.rb +128 -0
  67. data/spec/logstash/agent_spec.rb +62 -169
  68. data/spec/logstash/config/config_ast_spec.rb +2 -47
  69. data/spec/logstash/config/mixin_spec.rb +0 -157
  70. data/spec/logstash/filter_delegator_spec.rb +143 -0
  71. data/spec/logstash/inputs/metrics_spec.rb +52 -0
  72. data/spec/logstash/instrument/collector_spec.rb +49 -0
  73. data/spec/logstash/instrument/metric_spec.rb +110 -0
  74. data/spec/logstash/instrument/metric_store_spec.rb +163 -0
  75. data/spec/logstash/instrument/metric_type/counter_spec.rb +40 -0
  76. data/spec/logstash/instrument/metric_type/gauge_spec.rb +40 -0
  77. data/spec/logstash/instrument/namespaced_metric_spec.rb +25 -0
  78. data/spec/logstash/instrument/null_metric_spec.rb +9 -51
  79. data/spec/logstash/json_spec.rb +14 -0
  80. data/spec/logstash/output_delegator_spec.rb +6 -3
  81. data/spec/logstash/outputs/base_spec.rb +0 -107
  82. data/spec/logstash/pipeline_spec.rb +204 -33
  83. data/spec/logstash/plugin_spec.rb +80 -15
  84. data/spec/logstash/runner_spec.rb +134 -38
  85. data/spec/logstash/shutdown_watcher_spec.rb +0 -1
  86. data/spec/logstash/util/duration_formatter_spec.rb +11 -0
  87. data/spec/logstash/util/java_version_spec.rb +10 -2
  88. data/spec/logstash/util_spec.rb +28 -0
  89. data/spec/support/matchers.rb +30 -0
  90. metadata +154 -20
  91. data/lib/logstash/logging/json.rb +0 -21
  92. data/lib/logstash/special_agent.rb +0 -8
  93. data/lib/logstash/util/safe_uri.rb +0 -50
  94. data/spec/logstash/codecs/base_spec.rb +0 -74
  95. data/spec/static/i18n_spec.rb +0 -25
@@ -1,18 +1,19 @@
1
1
  # encoding: utf-8
2
+ require "spec_helper"
2
3
  require "logstash/plugin"
4
+ require "logstash/outputs/base"
5
+ require "logstash/codecs/base"
3
6
  require "logstash/inputs/base"
4
7
  require "logstash/filters/base"
5
- require "logstash/outputs/base"
6
- require "spec_helper"
7
8
 
8
9
  describe LogStash::Plugin do
9
10
  it "should fail lookup on inexisting type" do
10
- #expect_any_instance_of(Cabin::Channel).to receive(:debug).once
11
+ expect_any_instance_of(Cabin::Channel).to receive(:debug).once
11
12
  expect { LogStash::Plugin.lookup("badbadtype", "badname") }.to raise_error(LogStash::PluginLoadingError)
12
13
  end
13
14
 
14
15
  it "should fail lookup on inexisting name" do
15
- #expect_any_instance_of(Cabin::Channel).to receive(:debug).once
16
+ expect_any_instance_of(Cabin::Channel).to receive(:debug).once
16
17
  expect { LogStash::Plugin.lookup("filter", "badname") }.to raise_error(LogStash::PluginLoadingError)
17
18
  end
18
19
 
@@ -169,26 +170,90 @@ describe LogStash::Plugin do
169
170
 
170
171
  end
171
172
  end
172
- context "Collecting Metric in the plugin" do
173
- [LogStash::Inputs::Base, LogStash::Filters::Base, LogStash::Outputs::Base].each do |type|
173
+
174
+ describe "#id" do
175
+ plugin_types = [
176
+ LogStash::Filters::Base,
177
+ LogStash::Codecs::Base,
178
+ LogStash::Outputs::Base,
179
+ LogStash::Inputs::Base
180
+ ]
181
+
182
+ plugin_types.each do |plugin_type|
174
183
  let(:plugin) do
175
- Class.new(type) do
176
- config_name "goku"
184
+ Class.new(plugin_type) do
185
+ config_name "simple_plugin"
186
+
187
+ config :host, :validate => :string
188
+ config :export, :validte => :boolean
189
+
190
+ def register; end
191
+ end
192
+ end
193
+
194
+ let(:config) do
195
+ {
196
+ "host" => "127.0.0.1",
197
+ "export" => true
198
+ }
199
+ end
200
+
201
+ subject { plugin.new(config) }
202
+
203
+ context "plugin type is #{plugin_type}" do
204
+ context "when there is not ID configured for the output" do
205
+ it "it uses a UUID to identify this plugins" do
206
+ expect(subject.id).not_to eq(nil)
207
+ end
177
208
 
178
- def register
179
- metric.gauge("power-level", 9000)
209
+ it "will be different between instance of plugins" do
210
+ expect(subject.id).not_to eq(plugin.new(config).id)
211
+ end
212
+ end
213
+
214
+ context "When a user provide an ID for the plugin" do
215
+ let(:id) { "ABC" }
216
+ let(:config) { super.merge("id" => id) }
217
+
218
+ it "uses the user provided ID" do
219
+ expect(subject.id).to eq(id)
180
220
  end
181
221
  end
182
222
  end
223
+ end
224
+ end
183
225
 
184
- subject { plugin.new }
226
+ describe "#plugin_unique_name" do
227
+ let(:plugin) do
228
+ Class.new(LogStash::Filters::Base,) do
229
+ config_name "simple_plugin"
230
+ config :host, :validate => :string
185
231
 
186
- it "should not raise an exception when recoding a metric" do
187
- expect { subject.register }.not_to raise_error
232
+ def register; end
188
233
  end
234
+ end
235
+
236
+ let(:config) do
237
+ {
238
+ "host" => "127.0.0.1"
239
+ }
240
+ end
241
+
242
+ context "when the id is provided" do
243
+ let(:my_id) { "mysuper-plugin" }
244
+ let(:config) { super.merge({ "id" => my_id })}
245
+ subject { plugin.new(config) }
246
+
247
+ it "return a human readable ID" do
248
+ expect(subject.plugin_unique_name).to eq("simple_plugin_#{my_id}")
249
+ end
250
+ end
251
+
252
+ context "when the id is not provided provided" do
253
+ subject { plugin.new(config) }
189
254
 
190
- it "should use a `NullMetric`" do
191
- expect(subject.metric).to be_kind_of(LogStash::Instrument::NullMetric)
255
+ it "return a human readable ID" do
256
+ expect(subject.plugin_unique_name).to match(/^simple_plugin_/)
192
257
  end
193
258
  end
194
259
  end
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  require "logstash/runner"
4
4
  require "stud/task"
5
5
  require "stud/trap"
6
- require "stud/temporary"
6
+ require "logstash/util/java_version"
7
7
 
8
8
  class NullRunner
9
9
  def run(args); end
@@ -11,73 +11,169 @@ end
11
11
 
12
12
  describe LogStash::Runner do
13
13
 
14
+ subject { LogStash::Runner }
14
15
  let(:channel) { Cabin::Channel.new }
15
16
 
16
17
  before :each do
17
18
  allow(Cabin::Channel).to receive(:get).with(LogStash).and_return(channel)
18
- allow(channel).to receive(:subscribe).with(any_args).and_call_original
19
+ allow(channel).to receive(:subscribe).with(any_args)
19
20
  end
20
21
 
21
- context "argument parsing" do
22
- it "should run agent" do
23
- expect(Stud::Task).to receive(:new).once.and_return(nil)
24
- args = ["agent", "-e", ""]
25
- expect(subject.run(args)).to eq(nil)
22
+ describe "argument parsing" do
23
+ subject { LogStash::Runner.new("") }
24
+ context "when -e is given" do
25
+
26
+ let(:args) { ["-e", "input {} output {}"] }
27
+ let(:agent) { double("agent") }
28
+ let(:agent_logger) { double("agent logger") }
29
+
30
+ before do
31
+ allow(agent).to receive(:logger=).with(anything)
32
+ allow(agent).to receive(:shutdown)
33
+ allow(agent).to receive(:register_pipeline)
34
+ end
35
+
36
+ it "should execute the agent" do
37
+ expect(subject).to receive(:create_agent).and_return(agent)
38
+ expect(agent).to receive(:execute).once
39
+ subject.run(args)
40
+ end
41
+ end
42
+
43
+ context "with no arguments" do
44
+ let(:args) { [] }
45
+ let(:agent) { double("agent") }
46
+
47
+ before(:each) do
48
+ allow(LogStash::Agent).to receive(:new).and_return(agent)
49
+ allow(LogStash::Util::JavaVersion).to receive(:warn_on_bad_java_version)
50
+ end
51
+
52
+ it "should show help" do
53
+ expect($stderr).to receive(:puts).once
54
+ expect(subject).to receive(:signal_usage_error).once.and_call_original
55
+ expect(subject).to receive(:show_short_help).once
56
+ subject.run(args)
57
+ end
26
58
  end
59
+ end
27
60
 
28
- it "should run agent help" do
29
- expect(subject).to receive(:show_help).once.and_return(nil)
30
- args = ["agent", "-h"]
31
- expect(subject.run(args).wait).to eq(0)
61
+ context "--pluginpath" do
62
+ subject { LogStash::Runner.new("") }
63
+ let(:single_path) { "/some/path" }
64
+ let(:multiple_paths) { ["/some/path1", "/some/path2"] }
65
+
66
+ it "should add single valid dir path to the environment" do
67
+ expect(File).to receive(:directory?).and_return(true)
68
+ expect(LogStash::Environment).to receive(:add_plugin_path).with(single_path)
69
+ subject.configure_plugin_paths(single_path)
32
70
  end
33
71
 
34
- it "should show help with no arguments" do
35
- expect($stderr).to receive(:puts).once.and_return("No command given")
36
- expect($stderr).to receive(:puts).once
37
- args = []
38
- expect(subject.run(args).wait).to eq(1)
72
+ it "should fail with single invalid dir path" do
73
+ expect(File).to receive(:directory?).and_return(false)
74
+ expect(LogStash::Environment).not_to receive(:add_plugin_path)
75
+ expect{subject.configure_plugin_paths(single_path)}.to raise_error(Clamp::UsageError)
39
76
  end
40
77
 
41
- it "should show help for unknown commands" do
42
- expect($stderr).to receive(:puts).once.and_return("No such command welp")
43
- expect($stderr).to receive(:puts).once
44
- args = ["welp"]
45
- expect(subject.run(args).wait).to eq(1)
78
+ it "should add multiple valid dir path to the environment" do
79
+ expect(File).to receive(:directory?).exactly(multiple_paths.size).times.and_return(true)
80
+ multiple_paths.each{|path| expect(LogStash::Environment).to receive(:add_plugin_path).with(path)}
81
+ subject.configure_plugin_paths(multiple_paths)
46
82
  end
47
83
  end
48
84
 
49
85
  context "--auto-reload" do
86
+ subject { LogStash::Runner.new("") }
50
87
  context "when -f is not given" do
51
88
 
52
- let(:args) { ["agent", "-r", "-e", "input {} output {}"] }
89
+ let(:args) { ["-r", "-e", "input {} output {}"] }
53
90
 
54
91
  it "should exit immediately" do
55
- expect(subject.run(args).wait).to eq(1)
92
+ expect(subject).to receive(:signal_usage_error).and_call_original
93
+ expect(subject).to receive(:show_short_help)
94
+ expect(subject.run(args)).to eq(1)
56
95
  end
57
96
  end
58
97
  end
59
98
 
60
- context "--log-in-json" do
61
- let(:logfile) { Stud::Temporary.file }
62
- let(:args) { [ "agent", "--log-in-json", "-l", logfile.path, "-e", "some-invalid-config" ] }
99
+ describe "--config-test" do
100
+ subject { LogStash::Runner.new("") }
101
+ let(:args) { ["-t", "-e", pipeline_string] }
63
102
 
64
- after do
65
- logfile.close
66
- File.unlink(logfile.path)
103
+ context "with a good configuration" do
104
+ let(:pipeline_string) { "input { } filter { } output { }" }
105
+ it "should exit successfuly" do
106
+ expect(channel).to receive(:terminal)
107
+ expect(subject.run(args)).to eq(0)
108
+ end
67
109
  end
68
110
 
69
- before do
70
- expect(channel).to receive(:subscribe).with(kind_of(LogStash::Logging::JSON)).and_call_original
71
- subject.run(args).wait
111
+ context "with a bad configuration" do
112
+ let(:pipeline_string) { "rlwekjhrewlqrkjh" }
113
+ it "should fail by returning a bad exit code" do
114
+ expect(channel).to receive(:fatal)
115
+ expect(subject.run(args)).to eq(1)
116
+ end
117
+ end
118
+ end
119
+ describe "pipeline settings" do
120
+ let(:pipeline_string) { "input { stdin {} } output { stdout {} }" }
121
+ let(:main_pipeline_settings) { { :pipeline_id => "main" } }
122
+ let(:pipeline) { double("pipeline") }
123
+
124
+ before(:each) do
125
+ allow_any_instance_of(LogStash::Agent).to receive(:execute).and_return(true)
126
+ task = Stud::Task.new { 1 }
127
+ allow(pipeline).to receive(:run).and_return(task)
128
+ allow(pipeline).to receive(:shutdown)
129
+ end
130
+
131
+ context "when :pipeline_workers is not defined by the user" do
132
+ it "should not pass the value to the pipeline" do
133
+ expect(LogStash::Pipeline).to receive(:new).once.with(pipeline_string, hash_excluding(:pipeline_workers)).and_return(pipeline)
134
+
135
+ args = ["-e", pipeline_string]
136
+ subject.run("bin/logstash", args)
137
+ end
138
+ end
72
139
 
73
- # Log file should have stuff in it.
74
- expect(logfile.stat.size).to be > 0
140
+ context "when :pipeline_workers is defined by the user" do
141
+ it "should pass the value to the pipeline" do
142
+ main_pipeline_settings[:pipeline_workers] = 2
143
+ expect(LogStash::Pipeline).to receive(:new).with(pipeline_string, hash_including(main_pipeline_settings)).and_return(pipeline)
144
+
145
+ args = ["-w", "2", "-e", pipeline_string]
146
+ subject.run("bin/logstash", args)
147
+ end
75
148
  end
76
149
 
77
- it "should log in valid json. One object per line." do
78
- logfile.each_line do |line|
79
- expect(line).not_to be_empty
80
- expect { LogStash::Json.load(line) }.not_to raise_error
150
+ describe "debug_config" do
151
+ it "should set 'debug_config' to false by default" do
152
+ expect(LogStash::Config::Loader).to receive(:new).with(anything, false).and_call_original
153
+ expect(LogStash::Pipeline).to receive(:new).with(pipeline_string, hash_including(:debug_config => false)).and_return(pipeline)
154
+ args = ["--debug", "-e", pipeline_string]
155
+ subject.run("bin/logstash", args)
156
+ end
157
+
158
+ it "should allow overriding debug_config" do
159
+ expect(LogStash::Config::Loader).to receive(:new).with(anything, true).and_call_original
160
+ expect(LogStash::Pipeline).to receive(:new).with(pipeline_string, hash_including(:debug_config => true)).and_return(pipeline)
161
+ args = ["--debug", "--debug-config", "-e", pipeline_string]
162
+ subject.run("bin/logstash", args)
163
+ end
164
+ end
165
+
166
+ context "when configuring environment variable support" do
167
+ it "should set 'allow_env' to false by default" do
168
+ args = ["-e", pipeline_string]
169
+ expect(LogStash::Pipeline).to receive(:new).with(pipeline_string, hash_including(:allow_env => false)).and_return(pipeline)
170
+ subject.run("bin/logstash", args)
171
+ end
172
+
173
+ it "should support templating environment variables" do
174
+ args = ["-e", pipeline_string, "--allow-env"]
175
+ expect(LogStash::Pipeline).to receive(:new).with(pipeline_string, hash_including(:allow_env => true)).and_return(pipeline)
176
+ subject.run("bin/logstash", args)
81
177
  end
82
178
  end
83
179
  end
@@ -20,7 +20,6 @@ describe LogStash::ShutdownWatcher do
20
20
  allow(pipeline).to receive(:thread).and_return(Thread.current)
21
21
  allow(reporter).to receive(:snapshot).and_return(reporter_snapshot)
22
22
  allow(reporter_snapshot).to receive(:o_simple_hash).and_return({})
23
- allow(reporter_snapshot).to receive(:to_json_data).and_return("reporter-double")
24
23
 
25
24
  allow(subject).to receive(:pipeline_report_snapshot).and_wrap_original do |m, *args|
26
25
  report_count += 1
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ require "logstash/util/duration_formatter"
3
+ require "spec_helper"
4
+
5
+ describe LogStash::Util::DurationFormatter do
6
+ let(:duration) { 3600 * 1000 } # in milliseconds
7
+
8
+ it "returns a human format" do
9
+ expect(subject.human_format(duration)).to eq("1h")
10
+ end
11
+ end
@@ -19,10 +19,18 @@ describe "LogStash::Util::JavaVersion" do
19
19
  expect(mod.bad_java_version?("1.6.0")).to be_truthy
20
20
  end
21
21
 
22
+ it "should mark java 7 version as bad" do
23
+ expect(mod.bad_java_version?("1.7.0_51")).to be_truthy
24
+ end
25
+
26
+ it "should mark java version 8 as good" do
27
+ expect(mod.bad_java_version?("1.8.0")).to be_falsey
28
+ end
29
+
22
30
  it "should mark a good standard java version as good" do
23
- expect(mod.bad_java_version?("1.7.0_51")).to be_falsey
31
+ expect(mod.bad_java_version?("1.8.0_65")).to be_falsey
24
32
  end
25
-
33
+
26
34
  it "should mark a good beta version as good" do
27
35
  expect(mod.bad_java_version?("1.8.0-beta")).to be_falsey
28
36
  end
@@ -3,8 +3,18 @@ require 'spec_helper'
3
3
 
4
4
  require "logstash/util"
5
5
 
6
+ class ClassNameTest
7
+ end
8
+
9
+ module TestingClassName
10
+ class TestKlass
11
+ end
12
+ end
13
+
6
14
  describe LogStash::Util do
7
15
 
16
+ subject { described_class }
17
+
8
18
  context "stringify_keys" do
9
19
  it "should convert hash symbol keys to strings" do
10
20
  expect(LogStash::Util.stringify_symbols({:a => 1, "b" => 2})).to eq({"a" => 1, "b" => 2})
@@ -32,4 +42,22 @@ describe LogStash::Util do
32
42
  expect(LogStash::Util.stringify_symbols([:a, [1, :b]])).to eq(["a", [1, "b"]])
33
43
  end
34
44
  end
45
+
46
+ describe ".class_name" do
47
+ context "when the class is a top level class" do
48
+ let(:klass) { ClassNameTest.new }
49
+
50
+ it "returns the name of the class" do
51
+ expect(subject.class_name(klass)).to eq("ClassNameTest")
52
+ end
53
+ end
54
+
55
+ context "when the class is nested inside modules" do
56
+ let(:klass) { TestingClassName::TestKlass.new }
57
+
58
+ it "returns the name of the class" do
59
+ expect(subject.class_name(klass)).to eq("TestKlass")
60
+ end
61
+ end
62
+ end
35
63
  end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require "rspec"
3
+ require "rspec/expectations"
4
+
5
+ RSpec::Matchers.define :be_a_metric_event do |namespace, type, *args|
6
+ match do
7
+ namespace == Array(actual[0]).concat(Array(actual[1])) &&
8
+ type == actual[2] &&
9
+ args == actual[3..-1]
10
+ end
11
+ end
12
+
13
+ # Match to test `NullObject` pattern
14
+ RSpec::Matchers.define :implement_interface_of do |type, key, value|
15
+ match do |actual|
16
+ all_instance_methods_implemented?
17
+ end
18
+
19
+ def missing_methods
20
+ expected.instance_methods.select { |method| !actual.instance_methods.include?(method) }
21
+ end
22
+
23
+ def all_instance_methods_implemented?
24
+ expected.instance_methods.all? { |method| actual.instance_methods.include?(method) }
25
+ end
26
+
27
+ failure_message do
28
+ "Expecting `#{expected}` to implements instance methods of `#{actual}`, missing methods: #{missing_methods.join(",")}"
29
+ end
30
+ end