|
| 1 | +require "spec_helper" |
| 2 | +require "shifty/testing" |
| 3 | + |
| 4 | +module Shifty |
| 5 | + RSpec.describe Testing do |
| 6 | + include DSL |
| 7 | + |
| 8 | + after { Shifty.reset_configuration! } |
| 9 | + |
| 10 | + describe ".run" do |
| 11 | + context "feeds inputs through the framework and collects outputs" do |
| 12 | + Given(:worker) { relay_worker { |v| v.upcase } } |
| 13 | + When(:outputs) { Testing.run(worker, inputs: ["a", "b", "c"]) } |
| 14 | + Then { expect(outputs).to eq(["A", "B", "C"]) } |
| 15 | + end |
| 16 | + |
| 17 | + context "exercises the worker's effective policy (parity with production)" do |
| 18 | + Given(:mutator) { Worker.new { |v| v && v << :x } } |
| 19 | + Then do |
| 20 | + expect { Testing.run(mutator, inputs: [[:a]]) } |
| 21 | + .to raise_error(PolicyViolation) |
| 22 | + end |
| 23 | + end |
| 24 | + |
| 25 | + context "a worker declaring :isolated runs under :isolated" do |
| 26 | + Given(:mutator) { Worker.new(policy: :isolated) { |v| v && v << :x } } |
| 27 | + When(:outputs) { Testing.run(mutator, inputs: [[:a]]) } |
| 28 | + Then { expect(outputs).to eq([[:a, :x]]) } |
| 29 | + end |
| 30 | + |
| 31 | + context "an explicit policy: override beats the worker's own declaration" do |
| 32 | + Given(:mutator) { Worker.new(policy: :shared) { |v| v && v << :x } } |
| 33 | + Then do |
| 34 | + expect { Testing.run(mutator, inputs: [[:a]], policy: :frozen) } |
| 35 | + .to raise_error(PolicyViolation) |
| 36 | + end |
| 37 | + And { expect(mutator.effective_policy).to eq(:shared) } |
| 38 | + end |
| 39 | + |
| 40 | + context "false is a legitimate payload, not end-of-stream" do |
| 41 | + # relay_worker's `value &&` guard passes false through untouched, |
| 42 | + # so the negator applies only to the two true inputs. |
| 43 | + Given(:flipper) { relay_worker { |v| !v } } |
| 44 | + When(:outputs) { Testing.run(flipper, inputs: [true, false, true]) } |
| 45 | + Then { expect(outputs).to eq([false, false, false]) } |
| 46 | + And { expect(outputs.length).to eq(3) } |
| 47 | + end |
| 48 | + |
| 49 | + context "raises a diagnostic when a worker never passes the nil sentinel through" do |
| 50 | + Given(:worker) { Worker.new { |v| v.to_s } } |
| 51 | + Then do |
| 52 | + expect { Testing.run(worker, inputs: [:a], max_shifts: 50) } |
| 53 | + .to raise_error(Shifty::Error, /end-of-stream sentinel/) |
| 54 | + end |
| 55 | + end |
| 56 | + |
| 57 | + context "collects a non-1:1 output stream (filter) until end of stream" do |
| 58 | + Given(:evens) { filter_worker { |v| v.even? } } |
| 59 | + When(:outputs) { Testing.run(evens, inputs: [1, 2, 3, 4]) } |
| 60 | + Then { expect(outputs).to eq([2, 4]) } |
| 61 | + end |
| 62 | + end |
| 63 | + |
| 64 | + describe ".mutates_input?" do |
| 65 | + context "an undumpable input raises a descriptive error" do |
| 66 | + Given(:worker) { relay_worker { |v| v } } |
| 67 | + Then do |
| 68 | + expect { Testing.mutates_input?(worker, proc {}) } |
| 69 | + .to raise_error(Shifty::Error, /deep-copyable/) |
| 70 | + end |
| 71 | + end |
| 72 | + |
| 73 | + context "a mutating task is detected" do |
| 74 | + Given(:worker) { Worker.new(policy: :shared) { |v| v << :x } } |
| 75 | + Then { expect(Testing.mutates_input?(worker, [:a])).to be true } |
| 76 | + end |
| 77 | + |
| 78 | + context "a non-destructive task is not" do |
| 79 | + Given(:worker) { relay_worker { |v| v + [:x] } } |
| 80 | + Then { expect(Testing.mutates_input?(worker, [:a])).to be false } |
| 81 | + end |
| 82 | + |
| 83 | + context "detects mutation even when the worker's own policy would hide it" do |
| 84 | + Given(:worker) { side_worker(policy: :isolated) { |v| v << :boo } } |
| 85 | + Then { expect(Testing.mutates_input?(worker, [:a])).to be true } |
| 86 | + end |
| 87 | + end |
| 88 | + |
| 89 | + describe "opt-in loading" do |
| 90 | + Then { expect(File.read(File.expand_path("../../lib/shifty.rb", __dir__))).not_to match(%r{shifty/(testing|rspec)}) } |
| 91 | + end |
| 92 | + end |
| 93 | +end |
0 commit comments