Files
ubicloud/spec/model/strand_spec.rb
Jeremy Evans eab39a62dd Use Strand constants for prepared statements
This avoids calling Database#prepared_statement, which is slower as it
uses a mutex.

This also avoids a race condition if two threads try to prepare the
same statement at the same time.  That doesn't cause a problem, but it
is still unnecessary work.

The reason I didn't do this initially is it results in preparing statements
even if you may not need to do so in the tests.  However, I think the
benefit of avoiding the mutex outweighs a minimal slowdown when running
invididual tests that would load strand.rb but not call
Strand#take_lease_and_reload.

This also uses better mocking when testing this, so we are not mocking
the prepared statement, we are actually causing a lease violation and
ensuring that the prepared statement detects it.
2025-06-27 01:57:33 +09:00

100 lines
3.0 KiB
Ruby

# frozen_string_literal: true
require_relative "spec_helper"
RSpec.describe Strand do
let(:st) {
described_class.new(id: described_class.generate_uuid,
prog: "Test",
label: "start")
}
context "when leasing" do
it "can take a lease only if one is not already taken" do
st.save_changes
did_it = st.take_lease_and_reload {
expect(st.take_lease_and_reload {
:never_happens
}).to be false
:did_it
}
expect(did_it).to be :did_it
end
it "clears cached instance state" do
st.save_changes
st.set(label: "smoke_test_0")
st.associations[:parent] = st
st2 = st.subject
expect(st.subject).to equal st2
expect(st.changed_columns).not_to be_empty
st.take_lease_and_reload {
expect(st.label).to eq "start"
expect(st.changed_columns).to be_empty
expect(st.associations).to be_empty
expect(st.subject.id).to eq st.id
expect(st.subject).not_to equal st2
}
end
it "does an integrity check that deleted records are gone" do
st.label = "hop_exit"
st.save_changes
expect(st).to receive(:exists?).and_return(true)
expect { st.run }.to raise_error RuntimeError, "BUG: strand with @deleted set still exists in the database"
end
it "does an integrity check that the lease was modified as expected" do
st.label = "napper"
st.save_changes
expect(st).to receive(:unsynchronized_run) do
st.this.update(lease: Sequel[:lease] + Sequel.cast("1 second", :interval))
end
expect(Clog).to receive(:emit).with("lease violated data").and_call_original
expect { st.run }.to raise_error RuntimeError, "BUG: lease violated"
end
end
it "can load a prog" do
expect(st.load).to be_instance_of Prog::Test
end
it "can hop" do
st.save_changes
st.label = "hop_entry"
expect(st).to receive(:load).and_return Prog::Test.new(st)
expect {
st.unsynchronized_run
}.to change(st, :label).from("hop_entry").to("hop_exit")
end
it "rejects prog names that are not in the right module" do
expect {
described_class.prog_verify(Object)
}.to raise_error RuntimeError, "BUG: prog must be in Prog module"
end
it "crashes if a label does not provide flow control" do
expect {
st.unsynchronized_run
}.to raise_error RuntimeError, "BUG: Prog Test#start did not provide flow control"
end
it "can run labels consecutively if a deadline is not reached" do
st.label = "hop_entry"
st.save_changes
expect {
st.run(10)
}.to change { [st.label, st.exitval] }.from(["hop_entry", nil]).to(["hop_exit", {msg: "hop finished"}])
end
it "logs end of strand if it took long" do
st.label = "napper"
st.save_changes
expect(Time).to receive(:now).and_return(Time.now - 10, Time.now, Time.now)
expect(Clog).to receive(:emit).with("finished strand").and_call_original
st.unsynchronized_run
end
end