logstash-core 5.0.2-java → 5.1.1.1-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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/gemspec_jars.rb +9 -0
  3. data/lib/logstash-core/logstash-core.jar +0 -0
  4. data/lib/logstash-core/logstash-core.rb +22 -0
  5. data/lib/logstash-core/version.rb +1 -1
  6. data/lib/logstash-core_jars.rb +20 -0
  7. data/lib/logstash/agent.rb +65 -14
  8. data/lib/logstash/api/commands/default_metadata.rb +2 -1
  9. data/lib/logstash/api/commands/stats.rb +3 -2
  10. data/lib/logstash/config/file.rb +0 -1
  11. data/lib/logstash/config/loader.rb +1 -0
  12. data/lib/logstash/config/mixin.rb +2 -6
  13. data/lib/logstash/environment.rb +25 -2
  14. data/lib/logstash/event_dispatcher.rb +40 -0
  15. data/lib/logstash/filter_delegator.rb +1 -1
  16. data/lib/logstash/filters/base.rb +10 -2
  17. data/lib/logstash/instrument/metric_store.rb +0 -1
  18. data/lib/logstash/instrument/metric_type/base.rb +0 -1
  19. data/lib/logstash/instrument/namespaced_null_metric.rb +54 -0
  20. data/lib/logstash/instrument/null_metric.rb +55 -46
  21. data/lib/logstash/instrument/periodic_poller/jvm.rb +26 -3
  22. data/lib/logstash/instrument/periodic_poller/load_average.rb +47 -0
  23. data/lib/logstash/instrument/snapshot.rb +0 -1
  24. data/lib/logstash/java_integration.rb +0 -1
  25. data/lib/logstash/logging/logger.rb +37 -4
  26. data/lib/logstash/outputs/base.rb +1 -1
  27. data/lib/logstash/patches.rb +1 -0
  28. data/lib/logstash/patches/exception_to_json.rb +5 -0
  29. data/lib/logstash/pipeline.rb +50 -17
  30. data/lib/logstash/plugin.rb +14 -48
  31. data/lib/logstash/plugins/hooks_registry.rb +57 -0
  32. data/lib/logstash/plugins/registry.rb +208 -45
  33. data/lib/logstash/runner.rb +10 -5
  34. data/lib/logstash/settings.rb +101 -9
  35. data/lib/logstash/universal_plugin.rb +13 -0
  36. data/lib/logstash/util/byte_value.rb +60 -0
  37. data/lib/logstash/util/loggable.rb +14 -2
  38. data/lib/logstash/util/safe_uri.rb +1 -0
  39. data/lib/logstash/util/time_value.rb +70 -0
  40. data/lib/logstash/util/wrapped_acked_queue.rb +347 -0
  41. data/lib/logstash/util/wrapped_synchronous_queue.rb +17 -33
  42. data/lib/logstash/version.rb +1 -1
  43. data/locales/en.yml +1 -1
  44. data/logstash-core.gemspec +13 -18
  45. data/spec/api/lib/api/node_stats_spec.rb +3 -1
  46. data/spec/api/lib/api/support/resource_dsl_methods.rb +14 -6
  47. data/spec/api/spec_helper.rb +1 -0
  48. data/spec/conditionals_spec.rb +3 -2
  49. data/spec/logstash/agent_spec.rb +142 -62
  50. data/spec/logstash/environment_spec.rb +38 -0
  51. data/spec/logstash/event_dispatcher_spec.rb +76 -0
  52. data/spec/logstash/filter_delegator_spec.rb +2 -1
  53. data/spec/logstash/instrument/namespaced_null_metric_spec.rb +33 -0
  54. data/spec/logstash/instrument/null_metric_spec.rb +9 -5
  55. data/spec/logstash/instrument/periodic_poller/jvm_spec.rb +40 -0
  56. data/spec/logstash/instrument/periodic_poller/load_average_spec.rb +91 -0
  57. data/spec/logstash/output_delegator_spec.rb +2 -1
  58. data/spec/logstash/patches_spec.rb +15 -4
  59. data/spec/logstash/pipeline_pq_file_spec.rb +131 -0
  60. data/spec/logstash/pipeline_spec.rb +21 -17
  61. data/spec/logstash/plugin_spec.rb +4 -16
  62. data/spec/logstash/plugins/hooks_registry_spec.rb +60 -0
  63. data/spec/logstash/plugins/registry_spec.rb +22 -14
  64. data/spec/logstash/settings/bytes_spec.rb +53 -0
  65. data/spec/logstash/settings/time_value_spec.rb +31 -0
  66. data/spec/logstash/settings/writable_directory_spec.rb +125 -0
  67. data/spec/logstash/settings_spec.rb +39 -0
  68. data/spec/logstash/util/byte_value_spec.rb +33 -0
  69. data/spec/logstash/util/time_value_spec.rb +59 -0
  70. data/spec/logstash/util/wrapped_synchronous_queue_spec.rb +2 -2
  71. data/spec/logstash/webserver_spec.rb +4 -7
  72. data/spec/support/helpers.rb +8 -0
  73. data/spec/support/mocks_classes.rb +61 -31
  74. metadata +73 -20
  75. data/lib/jars.rb +0 -7
  76. data/lib/logstash/config/registry.rb +0 -13
  77. data/lib/logstash/inputs/metrics.rb +0 -47
  78. data/spec/logstash/inputs/metrics_spec.rb +0 -51
  79. data/vendor/jars/com/fasterxml/jackson/core/jackson-core/2.7.4/jackson-core-2.7.4.jar +0 -0
  80. data/vendor/jars/com/fasterxml/jackson/core/jackson-databind/2.7.4/jackson-databind-2.7.4.jar +0 -0
  81. data/vendor/jars/org/apache/logging/log4j/log4j-1.2-api/2.6.2/log4j-1.2-api-2.6.2.jar +0 -0
  82. data/vendor/jars/org/apache/logging/log4j/log4j-api/2.6.2/log4j-api-2.6.2.jar +0 -0
  83. data/vendor/jars/org/apache/logging/log4j/log4j-core/2.6.2/log4j-core-2.6.2.jar +0 -0
  84. data/vendor/jars/org/logstash/logstash-core/5.0.2/logstash-core-5.0.2.jar +0 -0
@@ -104,15 +104,15 @@ describe LogStash::Pipeline do
104
104
  describe "event cancellation" do
105
105
  # test harness for https://github.com/elastic/logstash/issues/6055
106
106
 
107
- let(:output) { DummyOutputWithEventsArray.new }
107
+ let(:output) { LogStash::Outputs::DummyOutputWithEventsArray.new }
108
108
 
109
109
  before do
110
110
  allow(LogStash::Plugin).to receive(:lookup).with("input", "generator").and_return(LogStash::Inputs::Generator)
111
- allow(LogStash::Plugin).to receive(:lookup).with("output", "dummyoutputwitheventsarray").and_return(DummyOutputWithEventsArray)
111
+ allow(LogStash::Plugin).to receive(:lookup).with("output", "dummyoutputwitheventsarray").and_return(LogStash::Outputs::DummyOutputWithEventsArray)
112
112
  allow(LogStash::Plugin).to receive(:lookup).with("filter", "drop").and_call_original
113
113
  allow(LogStash::Plugin).to receive(:lookup).with("filter", "mutate").and_call_original
114
114
  allow(LogStash::Plugin).to receive(:lookup).with("codec", "plain").and_call_original
115
- allow(DummyOutputWithEventsArray).to receive(:new).with(any_args).and_return(output)
115
+ allow(LogStash::Outputs::DummyOutputWithEventsArray).to receive(:new).with(any_args).and_return(output)
116
116
  end
117
117
 
118
118
  let(:config) do
@@ -447,6 +447,13 @@ describe LogStash::Pipeline do
447
447
  allow(settings).to receive(:get_value).with("pipeline.id").and_return("main")
448
448
  allow(settings).to receive(:get_value).with("metric.collect").and_return(false)
449
449
  allow(settings).to receive(:get_value).with("config.debug").and_return(false)
450
+ allow(settings).to receive(:get).with("queue.type").and_return("memory")
451
+ allow(settings).to receive(:get).with("queue.page_capacity").and_return(1024 * 1024)
452
+ allow(settings).to receive(:get).with("queue.max_events").and_return(250)
453
+ allow(settings).to receive(:get).with("queue.max_bytes").and_return(1024 * 1024 * 1024)
454
+ allow(settings).to receive(:get).with("queue.checkpoint.acks").and_return(1024)
455
+ allow(settings).to receive(:get).with("queue.checkpoint.writes").and_return(1024)
456
+ allow(settings).to receive(:get).with("queue.checkpoint.interval").and_return(1000)
450
457
 
451
458
  pipeline = LogStash::Pipeline.new(config, settings)
452
459
  expect(pipeline.metric).to be_kind_of(LogStash::Instrument::NullMetric)
@@ -509,7 +516,7 @@ describe LogStash::Pipeline do
509
516
  pipeline = LogStash::Pipeline.new(config, pipeline_settings_obj)
510
517
  Thread.new { pipeline.run }
511
518
  sleep 0.1 while !pipeline.ready?
512
- wait(5).for do
519
+ wait(3).for do
513
520
  # give us a bit of time to flush the events
514
521
  output.events.empty?
515
522
  end.to be_falsey
@@ -549,10 +556,11 @@ describe LogStash::Pipeline do
549
556
  end
550
557
 
551
558
  context "#started_at" do
559
+ # use a run limiting count to shutdown the pipeline automatically
552
560
  let(:config) do
553
561
  <<-EOS
554
562
  input {
555
- generator {}
563
+ generator { count => 10 }
556
564
  }
557
565
  EOS
558
566
  end
@@ -564,8 +572,7 @@ describe LogStash::Pipeline do
564
572
  end
565
573
 
566
574
  it "return when the pipeline started working" do
567
- t = Thread.new { subject.run }
568
- sleep(0.1)
575
+ subject.run
569
576
  expect(subject.started_at).to be < Time.now
570
577
  subject.shutdown
571
578
  end
@@ -604,7 +611,7 @@ describe LogStash::Pipeline do
604
611
 
605
612
  let(:pipeline_settings) { { "pipeline.id" => pipeline_id } }
606
613
  let(:pipeline_id) { "main" }
607
- let(:number_of_events) { 1000 }
614
+ let(:number_of_events) { 420 }
608
615
  let(:multiline_id) { "my-multiline" }
609
616
  let(:multiline_id_other) { "my-multiline_other" }
610
617
  let(:dummy_output_id) { "my-dummyoutput" }
@@ -648,13 +655,10 @@ describe LogStash::Pipeline do
648
655
 
649
656
  Thread.new { subject.run }
650
657
  # make sure we have received all the generated events
651
-
652
- times = 0
653
- while dummyoutput.events.size < number_of_events
654
- times += 1
655
- sleep 0.25
656
- raise "Waited too long" if times > 4
657
- end
658
+ wait(3).for do
659
+ # give us a bit of time to flush the events
660
+ dummyoutput.events.size < number_of_events
661
+ end.to be_falsey
658
662
  end
659
663
 
660
664
  after :each do
@@ -685,7 +689,7 @@ describe LogStash::Pipeline do
685
689
  it "populates the filter metrics" do
686
690
  [multiline_id, multiline_id_other].map(&:to_sym).each do |id|
687
691
  [:in, :out].each do |metric_key|
688
- plugin_name = "multiline_#{id}".to_sym
692
+ plugin_name = id.to_sym
689
693
  expect(collected_metric[:stats][:pipelines][:main][:plugins][:filters][plugin_name][:events][metric_key].value).to eq(number_of_events)
690
694
  end
691
695
  end
@@ -703,7 +707,7 @@ describe LogStash::Pipeline do
703
707
 
704
708
  it "populates the name of the filter plugin" do
705
709
  [multiline_id, multiline_id_other].map(&:to_sym).each do |id|
706
- plugin_name = "multiline_#{id}".to_sym
710
+ plugin_name = id.to_sym
707
711
  expect(collected_metric[:stats][:pipelines][:main][:plugins][:filters][plugin_name][:name].value).to eq(LogStash::Filters::Multiline.config_name)
708
712
  end
709
713
  end
@@ -31,20 +31,8 @@ describe LogStash::Plugin do
31
31
  class LogStash::Filters::LadyGaga < LogStash::Filters::Base
32
32
  config_name "lady_gaga"
33
33
  end
34
- expect(LogStash::Plugin.lookup("filter", "lady_gaga")).to eq(LogStash::Filters::LadyGaga)
35
- end
36
-
37
- describe "plugin signup in the registry" do
38
-
39
- let(:registry) { LogStash::Registry.instance }
40
34
 
41
- it "should be present in the registry" do
42
- class LogStash::Filters::MyPlugin < LogStash::Filters::Base
43
- config_name "my_plugin"
44
- end
45
- path = "logstash/filters/my_plugin"
46
- expect(registry.registered?(path)).to eq(true)
47
- end
35
+ expect(LogStash::Plugin.lookup("filter", "lady_gaga")).to eq(LogStash::Filters::LadyGaga)
48
36
  end
49
37
 
50
38
  describe "#inspect" do
@@ -296,7 +284,7 @@ describe LogStash::Plugin do
296
284
  end
297
285
 
298
286
  it "should use a `NullMetric`" do
299
- expect(subject.metric).to be_kind_of(LogStash::Instrument::NullMetric)
287
+ expect(subject.metric).to be_kind_of(LogStash::Instrument::NamespacedNullMetric)
300
288
  end
301
289
  end
302
290
 
@@ -308,7 +296,7 @@ describe LogStash::Plugin do
308
296
  end
309
297
 
310
298
  it "should use a `NullMetric`" do
311
- expect(subject.metric).to be_kind_of(LogStash::Instrument::NullMetric)
299
+ expect(subject.metric).to be_kind_of(LogStash::Instrument::NamespacedNullMetric)
312
300
  end
313
301
  end
314
302
  end
@@ -338,7 +326,7 @@ describe LogStash::Plugin do
338
326
  end
339
327
 
340
328
  it "should use a `NullMetric`" do
341
- expect(subject.metric).to be_kind_of(LogStash::Instrument::NullMetric)
329
+ expect(subject.metric).to be_kind_of(LogStash::Instrument::NamespacedNullMetric)
342
330
  end
343
331
  end
344
332
  end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ require "logstash/event_dispatcher"
3
+ require "logstash/plugins/hooks_registry"
4
+
5
+ describe LogStash::Plugins::HooksRegistry do
6
+ class DummyEmitter
7
+ attr_reader :dispatcher
8
+
9
+ def initialize
10
+ @dispatcher = LogStash::EventDispatcher.new(self)
11
+ end
12
+
13
+ def do_work
14
+ dispatcher.fire(:do_work)
15
+ end
16
+ end
17
+
18
+ class DummyListener
19
+ def initialize
20
+ @work = false
21
+ end
22
+
23
+ def do_work(emitter = nil)
24
+ @work = true
25
+ end
26
+
27
+ def work?
28
+ @work
29
+ end
30
+ end
31
+
32
+ subject { described_class.new }
33
+
34
+ let(:emitter) { DummyEmitter.new }
35
+ let(:listener) { DummyListener.new }
36
+
37
+ it "allow to register an emitter" do
38
+ expect { subject.register_emitter(emitter.class, emitter.dispatcher) }.to change { subject.emmitters_count }.by(1)
39
+ end
40
+
41
+ it "allow to remove an emitter" do
42
+ subject.register_emitter(emitter.class, emitter.dispatcher)
43
+ expect { subject.remove_emitter(emitter.class)}.to change { subject.emmitters_count }.by(-1)
44
+ end
45
+
46
+ it "allow to register hooks to emitters" do
47
+ expect { subject.register_hooks(emitter.class, listener) }.to change { subject.hooks_count }.by(1)
48
+ expect { subject.register_hooks(emitter.class, listener) }.to change { subject.hooks_count(emitter.class) }.by(1)
49
+ end
50
+
51
+ it "link the emitter class to the listener" do
52
+ subject.register_emitter(emitter.class, emitter.dispatcher)
53
+ subject.register_hooks(emitter.class, listener)
54
+
55
+ expect(listener.work?).to be_falsey
56
+ emitter.do_work
57
+
58
+ expect(listener.work?).to be_truthy
59
+ end
60
+ end
@@ -8,15 +8,19 @@ class LogStash::Inputs::Dummy < LogStash::Inputs::Base
8
8
  config_name "dummy"
9
9
 
10
10
  def register; end
11
-
12
11
  end
13
12
 
14
- describe LogStash::Registry do
15
13
 
16
- let(:registry) { described_class.instance }
14
+ class LogStash::Inputs::NewPlugin < LogStash::Inputs::Base
15
+ config_name "new_plugin"
17
16
 
18
- context "when loading installed plugins" do
17
+ def register; end
18
+ end
19
+
20
+ describe LogStash::Plugins::Registry do
21
+ let(:registry) { described_class.new }
19
22
 
23
+ context "when loading installed plugins" do
20
24
  let(:plugin) { double("plugin") }
21
25
 
22
26
  it "should return the expected class" do
@@ -25,18 +29,13 @@ describe LogStash::Registry do
25
29
  end
26
30
 
27
31
  it "should raise an error if can not find the plugin class" do
28
- expect(LogStash::Registry::Plugin).to receive(:new).with("input", "elastic").and_return(plugin)
29
- expect(plugin).to receive(:path).and_return("logstash/input/elastic").twice
30
- expect(plugin).to receive(:installed?).and_return(true)
31
- expect { registry.lookup("input", "elastic") }.to raise_error(LoadError)
32
+ expect { registry.lookup("input", "do-not-exist-elastic") }.to raise_error(LoadError)
32
33
  end
33
34
 
34
35
  it "should load from registry is already load" do
35
- registry.lookup("input", "stdin")
36
- expect(registry).to receive(:registered?).and_return(true).once
37
- registry.lookup("input", "stdin")
38
- internal_registry = registry.instance_variable_get("@registry")
39
- expect(internal_registry).to include("logstash/inputs/stdin" => LogStash::Inputs::Stdin)
36
+ expect(registry.exists?(:input, "stdin")).to be_falsey
37
+ expect { registry.lookup("input", "new_plugin") }.to change { registry.size }.by(1)
38
+ expect { registry.lookup("input", "new_plugin") }.not_to change { registry.size }
40
39
  end
41
40
  end
42
41
 
@@ -53,5 +52,14 @@ describe LogStash::Registry do
53
52
  end
54
53
  end
55
54
 
56
- end
55
+ context "when loading plugin manually configured" do
56
+ it "should return the plugin" do
57
+ class SimplePlugin
58
+ end
57
59
 
60
+ expect { registry.lookup("filter", "simple_plugin") }.to raise_error(LoadError)
61
+ registry.add(:filter, "simple_plugin", SimplePlugin)
62
+ expect(registry.lookup("filter", "simple_plugin")).to eq(SimplePlugin)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ require "logstash/settings"
4
+
5
+ describe LogStash::Setting::Bytes do
6
+ let(:multipliers) do
7
+ {
8
+ "b" => 1,
9
+ "kb" => 1 << 10,
10
+ "mb" => 1 << 20,
11
+ "gb" => 1 << 30,
12
+ "tb" => 1 << 40,
13
+ "pb" => 1 << 50,
14
+ }
15
+ end
16
+
17
+ let(:number) { Flores::Random.number(0..1000) }
18
+ let(:unit) { Flores::Random.item(multipliers.keys) }
19
+ let(:default) { "0b" }
20
+
21
+ subject { described_class.new("a byte value", default, false) }
22
+
23
+ describe "#set" do
24
+
25
+ # Hard-coded test just to make sure at least one known case is working
26
+ context "when given '10mb'" do
27
+ it "returns 10485760" do
28
+ expect(subject.set("10mb")).to be == 10485760
29
+ end
30
+ end
31
+
32
+ context "when given a string" do
33
+ context "which is a valid byte unit" do
34
+ let(:text) { "#{number}#{unit}" }
35
+
36
+ before { subject.set(text) }
37
+
38
+ it "should coerce it to a Fixnum" do
39
+ expect(subject.value).to be_a(Fixnum)
40
+ end
41
+ end
42
+
43
+ context "which is not a valid byte unit" do
44
+ values = [ "hello world", "1234", "", "-__-" ]
45
+ values.each do |value|
46
+ it "should fail" do
47
+ expect { subject.set(value) }.to raise_error
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ require "logstash/settings"
4
+
5
+ describe LogStash::Setting::TimeValue do
6
+ subject { described_class.new("option", "-1") }
7
+ describe "#set" do
8
+ it "should coerce the default correctly" do
9
+ expect(subject.value).to eq(LogStash::Util::TimeValue.new(-1, :nanosecond).to_nanos)
10
+ end
11
+
12
+ context "when a value is given outside of possible_values" do
13
+ it "should raise an ArgumentError" do
14
+ expect { subject.set("invalid") }.to raise_error(ArgumentError)
15
+ end
16
+ end
17
+ context "when a value is given as a time value" do
18
+ it "should set the value" do
19
+ subject.set("18m")
20
+ expect(subject.value).to eq(LogStash::Util::TimeValue.new(18, :minute).to_nanos)
21
+ end
22
+ end
23
+
24
+ context "when a value is given as a nanosecond" do
25
+ it "should set the value" do
26
+ subject.set(5)
27
+ expect(subject.value).to eq(LogStash::Util::TimeValue.new(5, :nanosecond).to_nanos)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ require "logstash/settings"
4
+ require "tmpdir"
5
+ require "socket" # for UNIXSocket
6
+
7
+ describe LogStash::Setting::WritableDirectory do
8
+ let(:mode_rx) { 0555 }
9
+ # linux is 108, Macos is 104, so use a safe value
10
+ # Stud::Temporary.pathname, will exceed that size without adding anything
11
+ let(:parent) { File.join(Dir.tmpdir, Time.now.to_f.to_s) }
12
+ let(:path) { File.join(parent, "fancy") }
13
+
14
+ before { Dir.mkdir(parent) }
15
+ after { Dir.exist?(path) && Dir.unlink(path) rescue nil }
16
+ after { Dir.unlink(parent) }
17
+
18
+ shared_examples "failure" do
19
+ before { subject.set(path) }
20
+ it "should fail" do
21
+ expect { subject.validate_value }.to raise_error
22
+ end
23
+ end
24
+
25
+ subject do
26
+ # Create a new WritableDirectory setting with no default value strict
27
+ # disabled.
28
+ described_class.new("fancy.path", "", false)
29
+ end
30
+
31
+ describe "#value" do
32
+ before { subject.set(path) }
33
+
34
+ context "when the directory is missing" do
35
+
36
+ context "and the parent is writable" do
37
+ after {
38
+ Dir.unlink(path)
39
+ }
40
+ it "creates the directory" do
41
+ subject.value # need to invoke `#value` to make it do the work.
42
+ expect(::File.directory?(path)).to be_truthy
43
+ end
44
+ end
45
+
46
+ context "and the directory cannot be created" do
47
+ before { File.chmod(mode_rx, parent) }
48
+ it "should fail" do
49
+ expect { subject.value }.to raise_error
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "#set and #validate_value" do
56
+ context "when the directory exists" do
57
+ before { Dir.mkdir(path) }
58
+ after { Dir.unlink(path) }
59
+
60
+ context "and is writable" do
61
+ before { subject.set(path) }
62
+ # assume this spec already created a directory that's writable... fair? :)
63
+ it "should return true" do
64
+ expect(subject.validate_value).to be_truthy
65
+ end
66
+ end
67
+
68
+ context "but is not writable" do
69
+ before { File.chmod(0, path) }
70
+ it_behaves_like "failure"
71
+ end
72
+ end
73
+
74
+ context "when the path exists" do
75
+ after { File.unlink(path) }
76
+
77
+ context "but is a file" do
78
+ before { File.new(path, "w").close }
79
+ it_behaves_like "failure"
80
+ end
81
+
82
+ context "but is a socket" do
83
+ let(:socket) { UNIXServer.new(path) }
84
+ before { socket } # realize `socket` value
85
+ after { socket.close }
86
+ it_behaves_like "failure"
87
+ end
88
+
89
+ context "but is a symlink" do
90
+ before { File::symlink("whatever", path) }
91
+ it_behaves_like "failure"
92
+ end
93
+ end
94
+
95
+ context "when the directory is missing" do
96
+ # Create a path with at least one subdirectory we can try to fiddle with permissions
97
+
98
+ context "but can be created" do
99
+ before do
100
+ # If the path doesn't exist, we want to try creating it, so let's be
101
+ # extra careful and make sure the path doesn't exist yet.
102
+ expect(File.directory?(path)).to be_falsey
103
+ subject.set(path)
104
+ end
105
+
106
+ after do
107
+ Dir.unlink(path)
108
+ end
109
+
110
+ it "should return true" do
111
+ expect(subject.validate_value).to be_truthy
112
+ end
113
+ end
114
+
115
+ context "and cannot be created" do
116
+ before do
117
+ # Remove write permission on the parent
118
+ File.chmod(mode_rx, parent)
119
+ end
120
+
121
+ it_behaves_like "failure"
122
+ end
123
+ end
124
+ end
125
+ end