odba 1.2.0 → 1.2.2
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.
- checksums.yaml +4 -4
- data/.github/workflows/devenv.yml +14 -10
- data/.github/workflows/ruby.yml +1 -2
- data/.gitignore +4 -0
- data/History.md +46 -0
- data/devenv.nix +15 -2
- data/devenv.yaml +8 -0
- data/lib/odba/cache.rb +8 -2
- data/lib/odba/storage.rb +63 -5
- data/lib/odba/version.rb +1 -1
- data/test/test_cache.rb +49 -0
- data/test/test_drbwrapper.rb +7 -1
- data/test/test_storage.rb +29 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e275634934d76a06600ef71830600a6a6cd1e03c3f59069aa94f998f25d9a973
|
|
4
|
+
data.tar.gz: cf0ae8760286cafa23e4d1c1c37e304aa8a548daa8438c00e58c1f2492691743
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d7005efee025dddcf75ae2e80b50266482a322916b59a7bd25fa339149a5df762e87248d4351e80acf819550ab2738d2f3cb3d04046c759bddfb48418221b69
|
|
7
|
+
data.tar.gz: cd31af260988120f4ab9c4b73126896f8c0ce43d68b9b4da24ffd1e968fc8fd0c3564fec466ba5f1d2ecd36c5605afe2b3b124bcd5ef2eb990b7535bc29e3751
|
|
@@ -7,24 +7,28 @@ on:
|
|
|
7
7
|
jobs:
|
|
8
8
|
tests:
|
|
9
9
|
strategy:
|
|
10
|
+
# without this a failure on one OS cancels the other, hiding its result
|
|
11
|
+
fail-fast: false
|
|
10
12
|
matrix:
|
|
11
|
-
|
|
13
|
+
# macos-latest dropped: it costs ~8 minutes per run for a library that
|
|
14
|
+
# is deployed on Linux, and its Nix/devenv setup is the fragile part -
|
|
15
|
+
# not odba itself. Re-add it here if that ever becomes worth the wait.
|
|
16
|
+
os: [ubuntu-latest]
|
|
12
17
|
runs-on: ${{ matrix.os }}
|
|
13
18
|
|
|
14
19
|
steps:
|
|
15
20
|
- uses: actions/checkout@v4
|
|
16
|
-
- uses: cachix/install-nix-action@
|
|
17
|
-
- uses: cachix/cachix-action@
|
|
21
|
+
- uses: cachix/install-nix-action@v31
|
|
22
|
+
- uses: cachix/cachix-action@v16
|
|
18
23
|
with:
|
|
19
24
|
name: devenv
|
|
20
25
|
- name: Install devenv.sh
|
|
21
26
|
run: nix profile install nixpkgs#devenv
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
# devenv test builds the shell, runs any pre-commit hooks, brings the
|
|
29
|
+
# postgres service up and then runs enterTest, which waits for the server
|
|
30
|
+
# and runs test/example.rb against it. A separate step re-running
|
|
31
|
+
# example.rb duplicated that work and raced initdb a second time without
|
|
32
|
+
# waiting, which is what kept macOS red.
|
|
33
|
+
- name: Build the devenv shell and run test/example.rb against a real postgres
|
|
24
34
|
run: devenv test
|
|
25
|
-
|
|
26
|
-
- name: Run the example.rb which uses a real database
|
|
27
|
-
shell: devenv shell bash -- -e {0}
|
|
28
|
-
run: |
|
|
29
|
-
devenv up --detach
|
|
30
|
-
devenv shell bundle exec ruby test/example.rb
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -25,9 +25,8 @@ jobs:
|
|
|
25
25
|
matrix:
|
|
26
26
|
os: [ ubuntu]
|
|
27
27
|
ruby: [3.2, 3.3, 3.4]
|
|
28
|
-
continue-on-error: ${{ matrix.ruby.to_s == '3.3' || matrix.ruby.to_s == '3.4' }}
|
|
29
28
|
steps:
|
|
30
|
-
- uses: actions/checkout@
|
|
29
|
+
- uses: actions/checkout@v4
|
|
31
30
|
- uses: ruby/setup-ruby@v1
|
|
32
31
|
with:
|
|
33
32
|
ruby-version: ${{ matrix.ruby }}
|
data/.gitignore
CHANGED
data/History.md
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
## 1.2.2 / 31.08.2026
|
|
2
|
+
|
|
3
|
+
Two processes on one database handed out the same odba_id. Whichever wrote
|
|
4
|
+
last overwrote the other's row in `object`, and every reference to the lost
|
|
5
|
+
object then resolved to a foreign one - an Array where a domain object
|
|
6
|
+
belonged, or the reverse. The referring instance variable stays correct and
|
|
7
|
+
points at the right number; a different object simply sits under it, so
|
|
8
|
+
searching the application for the offending assignment finds nothing.
|
|
9
|
+
|
|
10
|
+
* `Storage#next_id` takes the id from a Postgres sequence, `odba_id_seq`,
|
|
11
|
+
which `#setup` creates. It used to be `@next_id += 1` under a mutex, with
|
|
12
|
+
@next_id seeded once per process from the highest odba_id in the table -
|
|
13
|
+
sound for one process, wrong for every deployment running a web worker and
|
|
14
|
+
an import job against the same database. Measured with two processes side
|
|
15
|
+
by side, both answered `[61935067, 61935068, 61935069]`. Stores without the
|
|
16
|
+
sequence keep the old behaviour.
|
|
17
|
+
* The sequence starts at `MAX(odba_id)` plus `ID_SEQUENCE_GAP`, not at 1: a
|
|
18
|
+
plain `CREATE SEQUENCE` would re-issue ids that already exist, and the gap
|
|
19
|
+
covers ids that processes still on the old counter hold but have not
|
|
20
|
+
written yet.
|
|
21
|
+
* `Cache#next_id` no longer swallows `OdbaDuplicateIdError`. The guard was
|
|
22
|
+
there all along - a peer raises it when the id is taken and the method
|
|
23
|
+
retries - but `rescue` without a class caught it too, so the retry could
|
|
24
|
+
never run. Only `DRb::DRbError` is caught now, which is what the line was
|
|
25
|
+
for: an unreachable peer must not stop the allocation.
|
|
26
|
+
|
|
27
|
+
## 1.2.1 / 21.08.2026
|
|
28
|
+
|
|
29
|
+
No library changes: lib/ is identical to 1.2.0. This release only ships a test
|
|
30
|
+
suite and CI that pass.
|
|
31
|
+
|
|
32
|
+
* Cleared CacheEntry's @@id_table between cache tests. Its ObjectSpace
|
|
33
|
+
finalizer calls ODBA.cache.invalidate, so objects left by earlier tests were
|
|
34
|
+
collected at unpredictable moments and deleted ids the running test had just
|
|
35
|
+
stored - the long standing flakiness in test_clean, test_clean__prefetched
|
|
36
|
+
and test_transaction
|
|
37
|
+
* Fixed test_to_obj for drb >= 2.2, which resolves only ids to_id handed out
|
|
38
|
+
rather than looking them up with ObjectSpace._id2ref
|
|
39
|
+
* Bumped install-nix-action to v31: v26 cannot provision Nix build users on
|
|
40
|
+
current macOS images
|
|
41
|
+
* Stopped starting the processes twice in enterTest and waited for postgres
|
|
42
|
+
before running test/example.rb
|
|
43
|
+
* Declared the git-hooks input that current devenv no longer injects implicitly
|
|
44
|
+
* Dropped a continue-on-error that used matrix.ruby.to_s, which GitHub Actions
|
|
45
|
+
expressions do not support, so it silently never applied
|
|
46
|
+
|
|
1
47
|
## 1.2.0 / 21.08.2026
|
|
2
48
|
|
|
3
49
|
* Reconnect when the database connection was lost. ydbd-pg reports a lost
|
data/devenv.nix
CHANGED
|
@@ -47,7 +47,20 @@
|
|
|
47
47
|
languages.ruby.version = "3.4";
|
|
48
48
|
# See full reference at https://devenv.sh/reference/options/
|
|
49
49
|
enterTest = ''
|
|
50
|
-
devenv up
|
|
51
|
-
|
|
50
|
+
# No `devenv up` here: `devenv test` already starts the processes before
|
|
51
|
+
# running enterTest, so starting them again aborts the whole run with
|
|
52
|
+
# "Processes already running with PID ...".
|
|
53
|
+
#
|
|
54
|
+
# Those processes come up in parallel with this script. On slower runners
|
|
55
|
+
# initdb is still creating template0 when we arrive and the first connect
|
|
56
|
+
# is refused, so wait for postgres to accept connections. The bare
|
|
57
|
+
# pg_isready afterwards turns "never came up" into a clear error rather
|
|
58
|
+
# than a confusing PG::ConnectionBad from the test.
|
|
59
|
+
for _ in $(seq 1 60); do
|
|
60
|
+
if pg_isready --host 127.0.0.1 --port 5432 > /dev/null 2>&1; then break; fi
|
|
61
|
+
sleep 1
|
|
62
|
+
done
|
|
63
|
+
pg_isready --host 127.0.0.1 --port 5432
|
|
64
|
+
bundle exec ruby test/example.rb
|
|
52
65
|
'';
|
|
53
66
|
}
|
data/devenv.yaml
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
inputs:
|
|
2
2
|
nixpkgs:
|
|
3
3
|
url: github:NixOS/nixpkgs/nixos-24.11
|
|
4
|
+
# Declared explicitly because devenv no longer injects it implicitly: its
|
|
5
|
+
# git-hooks module throws "git-hooks or pre-commit-hooks input required".
|
|
6
|
+
# This mirrors what devenv.lock already records, so the lock stays valid.
|
|
7
|
+
git-hooks:
|
|
8
|
+
url: github:cachix/git-hooks.nix
|
|
9
|
+
inputs:
|
|
10
|
+
nixpkgs:
|
|
11
|
+
follows: nixpkgs
|
|
4
12
|
nixpkgs-ruby:
|
|
5
13
|
url: github:bobvanderlinden/nixpkgs-ruby
|
|
6
14
|
inputs:
|
data/lib/odba/cache.rb
CHANGED
|
@@ -441,8 +441,14 @@ module ODBA
|
|
|
441
441
|
end
|
|
442
442
|
@peers.each do |peer|
|
|
443
443
|
peer.reserve_next_id id
|
|
444
|
-
rescue
|
|
445
|
-
|
|
444
|
+
rescue DRb::DRbError
|
|
445
|
+
# A peer we cannot reach must not stop the allocation. Note the
|
|
446
|
+
# explicit class: a bare rescue here also swallowed the
|
|
447
|
+
# OdbaDuplicateIdError a peer raises when the id is already taken,
|
|
448
|
+
# so the retry below could never run and both processes kept the
|
|
449
|
+
# same id. Whichever wrote last overwrote the other's row in
|
|
450
|
+
# `object`, and every reference to it then resolved to a foreign
|
|
451
|
+
# object.
|
|
446
452
|
end
|
|
447
453
|
id
|
|
448
454
|
rescue OdbaDuplicateIdError
|
data/lib/odba/storage.rb
CHANGED
|
@@ -9,8 +9,17 @@ require "dbi"
|
|
|
9
9
|
module ODBA
|
|
10
10
|
class Storage # :nodoc: all
|
|
11
11
|
include Singleton
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
# Not attr_writer: whether the store has an id sequence is memoized, and
|
|
14
|
+
# that answer belongs to the connection it was asked on.
|
|
15
|
+
def dbi=(dbi)
|
|
16
|
+
@id_sequence = nil
|
|
17
|
+
@dbi = dbi
|
|
18
|
+
end
|
|
13
19
|
BULK_FETCH_STEP = 2500
|
|
20
|
+
# Distance between the highest id in use and the first the sequence
|
|
21
|
+
# hands out; see the odba_id_seq entry in TABLES.
|
|
22
|
+
ID_SEQUENCE_GAP = 100_000
|
|
14
23
|
TABLES = [
|
|
15
24
|
# in table 'object', the isolated dumps of all objects are stored
|
|
16
25
|
["object", <<~SQL],
|
|
@@ -37,12 +46,31 @@ module ODBA
|
|
|
37
46
|
CREATE INDEX IF NOT EXISTS target_id_index ON object_connection(target_id);
|
|
38
47
|
SQL
|
|
39
48
|
# helper table 'collection'
|
|
40
|
-
["collection", <<~SQL]
|
|
49
|
+
["collection", <<~SQL],
|
|
41
50
|
CREATE TABLE IF NOT EXISTS collection (
|
|
42
51
|
odba_id integer NOT NULL, key text, value text,
|
|
43
52
|
PRIMARY KEY(odba_id, key)
|
|
44
53
|
);
|
|
45
54
|
SQL
|
|
55
|
+
# The odba_id comes from this sequence, so that several processes on
|
|
56
|
+
# one database cannot hand out the same one. See #next_id.
|
|
57
|
+
#
|
|
58
|
+
# The start value is computed and must be: a plain CREATE SEQUENCE
|
|
59
|
+
# starts at 1 and would re-issue ids that already exist. The gap on
|
|
60
|
+
# top of MAX(odba_id) covers ids that processes still running with the
|
|
61
|
+
# old in-memory counter hold but have not written yet. Skipped numbers
|
|
62
|
+
# cost nothing - the odba_id is a surrogate key and carries no meaning.
|
|
63
|
+
["odba_id_seq", <<~SQL]
|
|
64
|
+
DO $$
|
|
65
|
+
BEGIN
|
|
66
|
+
IF NOT EXISTS (SELECT 1 FROM pg_class
|
|
67
|
+
WHERE relkind = 'S' AND relname = 'odba_id_seq') THEN
|
|
68
|
+
EXECUTE format('CREATE SEQUENCE odba_id_seq START WITH %s',
|
|
69
|
+
(SELECT COALESCE(MAX(odba_id), 0) + #{ID_SEQUENCE_GAP}
|
|
70
|
+
FROM object));
|
|
71
|
+
END IF;
|
|
72
|
+
END $$;
|
|
73
|
+
SQL
|
|
46
74
|
]
|
|
47
75
|
def initialize
|
|
48
76
|
@id_mutex = Mutex.new
|
|
@@ -425,13 +453,43 @@ module ODBA
|
|
|
425
453
|
end
|
|
426
454
|
end
|
|
427
455
|
|
|
456
|
+
# The id is allocated by the database, not by a counter in this process.
|
|
457
|
+
#
|
|
458
|
+
# It used to be `@next_id += 1` under this mutex, with @next_id seeded
|
|
459
|
+
# once per process from the highest odba_id in the table. That is sound
|
|
460
|
+
# for a single process and wrong for every deployment that runs more
|
|
461
|
+
# than one - web workers and import jobs on the same database each kept
|
|
462
|
+
# their own counter and handed out the same numbers, so one silently
|
|
463
|
+
# overwrote the other's row in `object`.
|
|
464
|
+
#
|
|
465
|
+
# Falls back to the old behaviour where no sequence exists, so a store
|
|
466
|
+
# that was never through #setup keeps working; #setup creates it.
|
|
428
467
|
def next_id
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
468
|
+
if id_sequence?
|
|
469
|
+
dbi.select_one("SELECT nextval('odba_id_seq')").first.to_i.tap { |id|
|
|
470
|
+
# max_id and reserve_next_id read @next_id, so keep it in step.
|
|
471
|
+
# Never backwards: a peer may already stand higher.
|
|
472
|
+
@id_mutex.synchronize {
|
|
473
|
+
@next_id = id if @next_id.nil? || @next_id < id
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else
|
|
477
|
+
@id_mutex.synchronize do
|
|
478
|
+
ensure_next_id_set
|
|
479
|
+
@next_id += 1
|
|
480
|
+
end
|
|
432
481
|
end
|
|
433
482
|
end
|
|
434
483
|
|
|
484
|
+
def id_sequence?
|
|
485
|
+
return @id_sequence unless @id_sequence.nil?
|
|
486
|
+
@id_sequence = dbi.select_one(
|
|
487
|
+
"SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = 'odba_id_seq'"
|
|
488
|
+
) ? true : false
|
|
489
|
+
rescue
|
|
490
|
+
@id_sequence = false
|
|
491
|
+
end
|
|
492
|
+
|
|
435
493
|
def update_max_id(id)
|
|
436
494
|
@id_mutex.synchronize do
|
|
437
495
|
@next_id = id
|
data/lib/odba/version.rb
CHANGED
data/test/test_cache.rb
CHANGED
|
@@ -19,6 +19,46 @@ module ODBA
|
|
|
19
19
|
public :load_object
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# The peer conflict must reach the retry. Until 1.2.2 the loop read
|
|
23
|
+
# `peer.reserve_next_id id rescue DRb::DRbError` - a rescue without a
|
|
24
|
+
# class, which caught the OdbaDuplicateIdError a peer raises when the id
|
|
25
|
+
# is taken. Both processes then kept the same id and one overwrote the
|
|
26
|
+
# other's row in `object`.
|
|
27
|
+
class TestCacheNextId < Test::Unit::TestCase
|
|
28
|
+
include FlexMock::TestCase
|
|
29
|
+
|
|
30
|
+
def setup
|
|
31
|
+
@cache = ODBA::Cache.instance
|
|
32
|
+
@cache.instance_variable_set(:@file_lock, false)
|
|
33
|
+
@storage = flexmock("storage")
|
|
34
|
+
ODBA.storage = @storage
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_a_peer_conflict_leads_to_a_new_id
|
|
38
|
+
@storage.should_receive(:next_id).and_return(100, 101)
|
|
39
|
+
seen = []
|
|
40
|
+
peer = flexmock("peer")
|
|
41
|
+
peer.should_receive(:reserve_next_id).and_return { |id|
|
|
42
|
+
seen << id
|
|
43
|
+
raise ODBA::OdbaDuplicateIdError, "taken" if seen.size == 1
|
|
44
|
+
true
|
|
45
|
+
}
|
|
46
|
+
@cache.instance_variable_set(:@peers, [peer])
|
|
47
|
+
assert_equal(101, @cache.next_id)
|
|
48
|
+
assert_equal([100, 101], seen)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A peer we cannot reach must not stop the allocation - that is what the
|
|
52
|
+
# line was for, and it stays.
|
|
53
|
+
def test_an_unreachable_peer_does_not_stop_the_allocation
|
|
54
|
+
@storage.should_receive(:next_id).and_return(100)
|
|
55
|
+
peer = flexmock("peer")
|
|
56
|
+
peer.should_receive(:reserve_next_id).and_raise(DRb::DRbError, "gone")
|
|
57
|
+
@cache.instance_variable_set(:@peers, [peer])
|
|
58
|
+
assert_equal(100, @cache.next_id)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
22
62
|
class TestCache < Test::Unit::TestCase
|
|
23
63
|
include FlexMock::TestCase
|
|
24
64
|
class ODBAContainerInCache
|
|
@@ -33,6 +73,15 @@ module ODBA
|
|
|
33
73
|
@cache.fetched = {}
|
|
34
74
|
@cache.prefetched = {}
|
|
35
75
|
@cache.indices = {}
|
|
76
|
+
## CacheEntry registers an ObjectSpace finalizer that calls
|
|
77
|
+
## ODBA.cache.invalidate, which deletes the odba_id from the shared
|
|
78
|
+
## singleton cache. Objects left behind by earlier tests are collected at
|
|
79
|
+
## unpredictable moments, so without dropping their finalizer bookkeeping
|
|
80
|
+
## a stray GC run deletes the very ids this test just stored. That is the
|
|
81
|
+
## long standing flakiness in test_clean, test_clean__prefetched and
|
|
82
|
+
## test_transaction: they failed on some Ruby versions and not others,
|
|
83
|
+
## and a different test failed on each run of identical code.
|
|
84
|
+
ODBA::CacheEntry.class_variable_set(:@@id_table, {})
|
|
36
85
|
end
|
|
37
86
|
|
|
38
87
|
def teardown
|
data/test/test_drbwrapper.rb
CHANGED
|
@@ -78,7 +78,13 @@ module ODBA
|
|
|
78
78
|
def test_to_obj
|
|
79
79
|
ODBA.cache.should_receive(:store)
|
|
80
80
|
o = Object.new
|
|
81
|
-
|
|
81
|
+
## drb >= 2.2 resolves only those ids it handed out itself: DRbIdConv
|
|
82
|
+
## keeps a DRbObjectSpace WeakMap that to_id populates, and to_obj raises
|
|
83
|
+
## RangeError for anything absent from it. Older drb looked the id up
|
|
84
|
+
## with ObjectSpace._id2ref, which resolved any live object. Register the
|
|
85
|
+
## object first, as real DRb traffic does - an id only reaches to_obj
|
|
86
|
+
## after to_id produced it.
|
|
87
|
+
assert_equal(o, @idconv.to_obj(@idconv.to_id(o)))
|
|
82
88
|
o.extend(ODBA::Persistable)
|
|
83
89
|
o.instance_variable_set(:@odba_id, 4)
|
|
84
90
|
o.odba_isolated_store
|
data/test/test_storage.rb
CHANGED
|
@@ -15,6 +15,7 @@ module ODBA
|
|
|
15
15
|
@storage = ODBA::Storage.instance
|
|
16
16
|
@dbi = flexmock("DBI")
|
|
17
17
|
@storage.dbi = @dbi
|
|
18
|
+
@storage.instance_variable_set(:@id_sequence, nil)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def teardown
|
|
@@ -106,12 +107,40 @@ module ODBA
|
|
|
106
107
|
@storage.create_index("index_name")
|
|
107
108
|
end
|
|
108
109
|
|
|
110
|
+
# Without a sequence the old counter still runs, so a store that was
|
|
111
|
+
# never through #setup keeps working.
|
|
109
112
|
def test_next_id
|
|
113
|
+
@dbi.should_receive(:select_one).and_return(nil)
|
|
110
114
|
@storage.next_id = 1
|
|
111
115
|
assert_equal(2, @storage.next_id)
|
|
112
116
|
assert_equal(3, @storage.next_id)
|
|
113
117
|
end
|
|
114
118
|
|
|
119
|
+
# The point of 1.2.2: the id comes from the database, so two processes
|
|
120
|
+
# on one store cannot be handed the same one.
|
|
121
|
+
def test_next_id__from_the_sequence
|
|
122
|
+
@dbi.should_receive(:select_one)
|
|
123
|
+
.with("SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = 'odba_id_seq'")
|
|
124
|
+
.once.and_return([1])
|
|
125
|
+
@dbi.should_receive(:select_one)
|
|
126
|
+
.with("SELECT nextval('odba_id_seq')").twice.and_return([4711], [4712])
|
|
127
|
+
assert_equal(4711, @storage.next_id)
|
|
128
|
+
assert_equal(4712, @storage.next_id)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# max_id and reserve_next_id read @next_id, so it has to follow the
|
|
132
|
+
# sequence - and never move backwards, a peer may stand higher already.
|
|
133
|
+
def test_next_id__keeps_the_local_counter_in_step
|
|
134
|
+
@dbi.should_receive(:select_one)
|
|
135
|
+
.with(/pg_class/).and_return([1])
|
|
136
|
+
@dbi.should_receive(:select_one).with(/nextval/).and_return([90], [5])
|
|
137
|
+
@storage.next_id = 10
|
|
138
|
+
@storage.next_id
|
|
139
|
+
assert_equal(90, @storage.instance_variable_get(:@next_id))
|
|
140
|
+
@storage.next_id
|
|
141
|
+
assert_equal(90, @storage.instance_variable_get(:@next_id))
|
|
142
|
+
end
|
|
143
|
+
|
|
115
144
|
def test_store__1
|
|
116
145
|
dbi = flexmock("dbi")
|
|
117
146
|
@storage.dbi = dbi
|