2019-12-11 22:41:23 +01:00
|
|
|
|
require "spec"
|
|
|
|
|
require "file_utils"
|
|
|
|
|
require "json"
|
|
|
|
|
require "uuid"
|
|
|
|
|
|
|
|
|
|
require "../src/*"
|
|
|
|
|
|
2019-12-17 18:16:13 +01:00
|
|
|
|
# FIXME: Split the test data in separate files. We don’t care about those here.
|
|
|
|
|
|
2019-12-11 22:41:23 +01:00
|
|
|
|
class Ship
|
|
|
|
|
include JSON::Serializable
|
|
|
|
|
|
|
|
|
|
def_clone
|
|
|
|
|
|
|
|
|
|
property id : String
|
2019-12-18 03:43:09 +01:00
|
|
|
|
property klass : String
|
2019-12-11 22:41:23 +01:00
|
|
|
|
property name : String
|
|
|
|
|
property tags : Array(String)
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
def initialize(@name, @klass = "<unknown>", @id = UUID.random.to_s, @tags = [] of String)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Makes testing arrays of this class easier.
|
|
|
|
|
def <=>(other)
|
|
|
|
|
@name <=> other.name
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Common, reusable test data.
|
|
|
|
|
# Those data can be indexed, partitioned or tagged on different parameters,
|
|
|
|
|
# and can easily be extended.
|
|
|
|
|
|
|
|
|
|
class_getter kisaragi = Ship.new("Kisaragi", "Mutsuki")
|
2020-01-10 17:20:10 +01:00
|
|
|
|
class_getter mutsuki = Ship.new("Mutsuki", "Mutsuki", tags: ["name ship"])
|
|
|
|
|
class_getter yayoi = Ship.new("Yayoi", "Mutsuki")
|
2019-12-11 22:41:23 +01:00
|
|
|
|
class_getter destroyers = [
|
2020-01-07 16:06:18 +01:00
|
|
|
|
@@mutsuki,
|
2020-01-10 17:20:10 +01:00
|
|
|
|
@@kisaragi,
|
|
|
|
|
@@yayoi,
|
2019-12-11 22:41:23 +01:00
|
|
|
|
Ship.new("Uzuki", "Mutsuki"),
|
|
|
|
|
Ship.new("Satsuki", "Mutsuki"),
|
|
|
|
|
|
|
|
|
|
Ship.new("Shiratsuyu", "Shiratsuyu", tags: ["name ship"]),
|
|
|
|
|
Ship.new("Murasame", "Shiratsuyu"),
|
|
|
|
|
Ship.new("Yuudachi", "Shiratsuyu")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
class_getter yamato =
|
|
|
|
|
Ship.new("Yamato", "Yamato", tags: ["name ship", "flagship"])
|
|
|
|
|
class_getter flagship : Ship = yamato
|
|
|
|
|
class_getter battleships = [
|
|
|
|
|
@@yamato,
|
|
|
|
|
Ship.new("Kongou", "Kongou", tags: ["name ship"]),
|
|
|
|
|
Ship.new("Haruna", "Kongou"),
|
|
|
|
|
Ship.new("Kirishima", "Kongou"),
|
|
|
|
|
Ship.new("Hiei" , "Kongou"),
|
|
|
|
|
Ship.new("Musashi", "Yamato"),
|
|
|
|
|
Ship.new("Shinano", "Yamato")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
class_getter all_ships : Array(Ship) = @@destroyers + @@battleships
|
|
|
|
|
|
|
|
|
|
# Equality is true if every property is identical.
|
|
|
|
|
def ==(other)
|
2019-12-18 03:43:09 +01:00
|
|
|
|
@id == other.id && @klass == other.klass && @name == other.name &&
|
2019-12-11 22:41:23 +01:00
|
|
|
|
@tags == other.tags
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-17 18:16:13 +01:00
|
|
|
|
# This will be used for migration testing, but basically it’s a variant of
|
|
|
|
|
# the class above, a few extra fields, a few missing ones.
|
|
|
|
|
class PrimitiveShip
|
|
|
|
|
include JSON::Serializable
|
|
|
|
|
|
|
|
|
|
property id : String
|
|
|
|
|
property name : String
|
|
|
|
|
property wooden : Bool = false # Will be removed.
|
|
|
|
|
property class_name : String # Will be renamed
|
|
|
|
|
property flagship : Bool = false # Will be moved to tags.
|
|
|
|
|
|
|
|
|
|
def initialize(@name, @class_name = "<unknown>", @id = UUID.random.to_s, @flagship = false)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class_getter kamikaze =
|
|
|
|
|
PrimitiveShip.new("Kamikaze", "Kamikaze")
|
|
|
|
|
class_getter asakaze =
|
|
|
|
|
PrimitiveShip.new("Asakaze", "Kamikaze")
|
|
|
|
|
class_getter all_ships : Array(PrimitiveShip) = [
|
|
|
|
|
@@kamikaze,
|
|
|
|
|
@@asakaze
|
|
|
|
|
]
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-28 03:24:05 +01:00
|
|
|
|
class DODB::SpecDataBase < DODB::DataBase(Ship)
|
2020-01-07 16:06:18 +01:00
|
|
|
|
def initialize(storage_ext = "", remove_previous_data = true)
|
2019-12-17 18:16:13 +01:00
|
|
|
|
storage_dir = "test-storage#{storage_ext}"
|
|
|
|
|
|
2020-01-07 16:06:18 +01:00
|
|
|
|
if remove_previous_data
|
|
|
|
|
::FileUtils.rm_rf storage_dir
|
|
|
|
|
end
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
2019-12-17 18:16:13 +01:00
|
|
|
|
super storage_dir
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-11 23:18:20 +01:00
|
|
|
|
describe "DODB::DataBase" do
|
2019-12-11 22:41:23 +01:00
|
|
|
|
describe "basics" do
|
|
|
|
|
it "store and get data" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db.to_a.sort.should eq(Ship.all_ships.sort)
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "rewrite already stored data" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
ship = Ship.all_ships[0]
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
key = db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db[key] = Ship.new "broken"
|
|
|
|
|
db[key] = ship
|
|
|
|
|
|
|
|
|
|
db[key].should eq(ship)
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "properly remove data" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db.pop
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
Ship.all_ships.each_with_index do |ship, i|
|
2019-12-11 22:41:23 +01:00
|
|
|
|
# FIXME: Should it raise a particular exception?
|
2019-12-11 23:18:20 +01:00
|
|
|
|
expect_raises DODB::MissingEntry do
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db[i]
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db[i]?.should be_nil
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
2020-01-07 16:06:18 +01:00
|
|
|
|
|
|
|
|
|
it "preserves data on reopening" do
|
|
|
|
|
db1 = DODB::SpecDataBase.new
|
|
|
|
|
db1 << Ship.kisaragi
|
|
|
|
|
|
|
|
|
|
db1.to_a.size.should eq(1)
|
|
|
|
|
|
|
|
|
|
db2 = DODB::SpecDataBase.new remove_previous_data: false
|
|
|
|
|
db2 << Ship.mutsuki
|
|
|
|
|
|
|
|
|
|
db1.to_a.size.should eq(2)
|
|
|
|
|
end
|
2020-01-10 17:20:10 +01:00
|
|
|
|
|
|
|
|
|
it "iterates in normal and reversed order" do
|
|
|
|
|
db = DODB::SpecDataBase.new
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
|
|
|
|
db << ship
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
db.each_with_index do |item, index|
|
|
|
|
|
item.should eq Ship.all_ships[index]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
db.reverse_each_with_index do |item, index|
|
|
|
|
|
item.should eq Ship.all_ships.reverse[index]
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "indices" do
|
|
|
|
|
it "do basic indexing" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_name = db.new_index "name", &.name
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
Ship.all_ships.each_with_index do |ship|
|
2019-12-11 22:41:23 +01:00
|
|
|
|
db_ships_by_name.get?(ship.name).should eq(ship)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "raise on index overload" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_name = db.new_index "name", &.name
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << Ship.kisaragi
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
# Should not be allowed to store an entry whose “name” field
|
|
|
|
|
# already exists.
|
2019-12-11 23:18:20 +01:00
|
|
|
|
expect_raises(DODB::IndexOverload) do
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << Ship.kisaragi
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "properly deindex" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_name = db.new_index "name", &.name
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
Ship.all_ships.each_with_index do |ship, i|
|
|
|
|
|
db.delete i
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
|
|
|
|
db_ships_by_name.get?(ship.name).should be_nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "properly reindex" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_name = db.new_index "name", &.name
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
key = db << Ship.kisaragi
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
# We give the old id to the new ship, to get it replaced in
|
|
|
|
|
# the database.
|
|
|
|
|
some_new_ship = Ship.all_ships[2].clone
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db[key] = some_new_ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db[key].should eq(some_new_ship)
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_name.get?(some_new_ship.name).should eq(some_new_ship)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "partitions" do
|
|
|
|
|
it "do basic partitioning" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db_ships_by_class = db.new_partition "class", &.klass
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db_ships_by_class.get(ship.klass).should contain(ship)
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# We extract the possible classes to do test on them.
|
2019-12-18 03:43:09 +01:00
|
|
|
|
ship_classes = Ship.all_ships.map(&.klass).uniq
|
2019-12-11 22:41:23 +01:00
|
|
|
|
ship_classes.each do |klass|
|
|
|
|
|
partition = db_ships_by_class.get klass
|
|
|
|
|
|
|
|
|
|
# A partition on “class” should contain entries that all
|
|
|
|
|
# share the same value of “class”.
|
2019-12-18 03:43:09 +01:00
|
|
|
|
partition.map(&.klass.==(klass)).reduce { |a, b|
|
2019-12-11 22:41:23 +01:00
|
|
|
|
a && b
|
|
|
|
|
}.should be_true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "tags" do
|
|
|
|
|
it "do basic tagging" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
db_ships_by_tags.get("flagship").should eq([Ship.flagship])
|
|
|
|
|
|
|
|
|
|
# All returned entries should have the requested tag.
|
|
|
|
|
db_ships_by_tags.get("name ship")
|
|
|
|
|
.map(&.tags.includes?("name ship"))
|
|
|
|
|
.reduce { |a, e| a && e }
|
|
|
|
|
.should be_true
|
|
|
|
|
|
|
|
|
|
# There shouldn’t be one in our data about WWII Japanese warships…
|
|
|
|
|
db_ships_by_tags.get("starship").should eq([] of Ship)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "properly removes tags" do
|
2019-12-11 23:18:20 +01:00
|
|
|
|
db = DODB::SpecDataBase.new
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Removing the “flagship” tag, brace for impact.
|
2019-12-18 03:43:09 +01:00
|
|
|
|
flagship, index = db_ships_by_tags.get_with_indices("flagship")[0]
|
2019-12-11 22:41:23 +01:00
|
|
|
|
flagship.tags = [] of String
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db[index] = flagship
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ship, index = db_ships_by_tags.update(tag: "flagship") do |ship, index|
|
|
|
|
|
# ship.tags = [] of String
|
|
|
|
|
# db[index] = ship
|
|
|
|
|
# end
|
2019-12-11 22:41:23 +01:00
|
|
|
|
|
|
|
|
|
db_ships_by_tags.get("flagship").should eq([] of Ship)
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-12-12 00:01:02 +01:00
|
|
|
|
|
|
|
|
|
describe "tools" do
|
|
|
|
|
it "rebuilds indexes" do
|
|
|
|
|
db = DODB::SpecDataBase.new
|
|
|
|
|
|
|
|
|
|
db_ships_by_name = db.new_index "name", &.name
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db_ships_by_class = db.new_partition "class", &.klass
|
2019-12-12 00:01:02 +01:00
|
|
|
|
db_ships_by_tags = db.new_tags "tags", &.tags
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db << ship
|
2019-12-12 00:01:02 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
db.reindex_everything!
|
|
|
|
|
|
|
|
|
|
Ship.all_ships.each do |ship|
|
|
|
|
|
db_ships_by_name.get?(ship.name).should eq(ship)
|
2019-12-18 03:43:09 +01:00
|
|
|
|
db_ships_by_class.get(ship.klass).should contain(ship)
|
2019-12-12 00:01:02 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-17 18:16:13 +01:00
|
|
|
|
it "migrates properly" do
|
2019-12-28 03:24:05 +01:00
|
|
|
|
old_db = DODB::DataBase(PrimitiveShip).new "test-storage-migration-origin"
|
2019-12-17 18:16:13 +01:00
|
|
|
|
|
|
|
|
|
old_ships_by_name = old_db.new_index "name", &.name
|
|
|
|
|
old_ships_by_class = old_db.new_partition "class", &.class_name
|
|
|
|
|
|
|
|
|
|
PrimitiveShip.all_ships.each do |ship|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
old_db << ship
|
2019-12-17 18:16:13 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# At this point, the “old” DB is filled. Now we need to convert
|
|
|
|
|
# to the new DB.
|
|
|
|
|
|
|
|
|
|
new_db = DODB::SpecDataBase.new "-migration-target"
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
new_ships_by_name = new_db.new_index "name", &.name
|
|
|
|
|
new_ships_by_class = new_db.new_partition "class", &.klass
|
2019-12-17 18:16:13 +01:00
|
|
|
|
new_ships_by_tags = new_db.new_tags "tags", &.tags
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
old_db.each_with_index do |ship, index|
|
2019-12-17 18:16:13 +01:00
|
|
|
|
new_ship = Ship.new ship.name,
|
2019-12-18 03:43:09 +01:00
|
|
|
|
klass: ship.class_name,
|
2019-12-17 18:16:13 +01:00
|
|
|
|
id: ship.id,
|
|
|
|
|
tags: Array(String).new.tap { |tags|
|
|
|
|
|
tags << "name ship" if ship.name == ship.class_name
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
new_db[index] = new_ship
|
2019-12-17 18:16:13 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# At this point, the conversion is done, so… we’re making a few
|
|
|
|
|
# arbitrary tests on the new data.
|
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
old_db.each_with_index do |old_ship, old_index|
|
|
|
|
|
ship = new_db[old_index]
|
2019-12-17 18:16:13 +01:00
|
|
|
|
|
|
|
|
|
ship.id.should eq(old_ship.id)
|
2019-12-18 03:43:09 +01:00
|
|
|
|
ship.klass.should eq(old_ship.class_name)
|
2019-12-17 18:16:13 +01:00
|
|
|
|
|
2019-12-18 03:43:09 +01:00
|
|
|
|
ship.tags.any?(&.==("name ship")).should be_true if ship.name == ship.klass
|
2019-12-17 18:16:13 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
2019-12-12 00:01:02 +01:00
|
|
|
|
end
|
2019-12-11 22:41:23 +01:00
|
|
|
|
end
|
|
|
|
|
|