Anki/tests/test_deck.py
Damien Elmes d3a3edb707 move models into the deck table
Like the previous change, models have been moved from a separate DB table to
an entry in the deck. We need them for many operations including reviewing,
and it's easier to keep them in memory than half on disk with a cache that
gets cleared every time we .reset(). This means they are easily serialized as
well - previously they were part Python and part JSON, which made access
confusing.

Because the data is all pulled from JSON now, the instance methods have been
moved to the model registry. Eg:
  model.addField(...) -> deck.models.addField(model, ...).

- IDs are now timestamped as with groups et al.

- The data field for plugins was also removed. Config info can be added to
  deck.conf; larger data should be stored externally.

- Upgrading needs to be updated for the new model structure.

- HexifyID() now accepts strings as well, as our IDs get converted to strings
  in the serialization process.
2011-08-27 22:27:09 +09:00

197 lines
5.7 KiB
Python

# coding: utf-8
import os, re, datetime
from tests.shared import assertException, getEmptyDeck, testDir
from anki.stdmodels import addBasicModel
from anki import Deck
newPath = None
newMod = None
def test_create():
global newPath, newMod
path = "/tmp/test_attachNew.anki"
try:
os.unlink(path)
except OSError:
pass
deck = Deck(path)
# for open()
newPath = deck.path
deck.save()
newMod = deck.mod
deck.close()
del deck
def test_open():
deck = Deck(newPath)
assert deck.mod == newMod
deck.close()
def test_openReadOnly():
# non-writeable dir
assertException(Exception,
lambda: Deck("/attachroot"))
# reuse tmp file from before, test non-writeable file
os.chmod(newPath, 0)
assertException(Exception,
lambda: Deck(newPath))
os.chmod(newPath, 0666)
os.unlink(newPath)
def test_factAddDelete():
deck = getEmptyDeck()
# add a fact
f = deck.newFact()
f['Front'] = u"one"; f['Back'] = u"two"
n = deck.addFact(f)
assert n == 1
deck.rollback()
assert deck.cardCount() == 0
# try with two cards
f = deck.newFact()
f['Front'] = u"one"; f['Back'] = u"two"
m = f.model()
m['tmpls'][1]['actv'] = True
deck.models.save(m)
n = deck.addFact(f)
assert n == 2
# check q/a generation
c0 = f.cards()[0]
assert re.sub("</?.+?>", "", c0.q()) == u"one"
# it should not be a duplicate
for p in f.problems():
assert not p
# now let's make a duplicate and test uniqueness
f2 = deck.newFact()
f2.model()['flds'][1]['req'] = True
f2['Front'] = u"one"; f2['Back'] = u""
p = f2.problems()
assert p[0] == "unique"
assert p[1] == "required"
# try delete the first card
cards = f.cards()
id1 = cards[0].id; id2 = cards[1].id
assert deck.cardCount() == 2
assert deck.factCount() == 1
deck.delCards([id1])
assert deck.cardCount() == 1
assert deck.factCount() == 1
# and the second should clear the fact
deck.delCards([id2])
assert deck.cardCount() == 0
assert deck.factCount() == 0
def test_fieldChecksum():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u"new"; f['Back'] = u"new2"
deck.addFact(f)
assert deck.db.scalar(
"select csum from fsums") == int("22af645d", 16)
# empty field should have no checksum
f['Front'] = u""
f.flush()
assert deck.db.scalar(
"select count() from fsums") == 0
# changing the val should change the checksum
f['Front'] = u"newx"
f.flush()
assert deck.db.scalar(
"select csum from fsums") == int("4b0e5a4c", 16)
# turning off unique and modifying the fact should delete the sum
m = f.model()
m['flds'][0]['uniq'] = False
deck.models.save(m)
f.flush()
assert deck.db.scalar(
"select count() from fsums") == 0
# and turning on both should ensure two checksums generated
m['flds'][0]['uniq'] = True
m['flds'][1]['uniq'] = True
deck.models.save(m)
f.flush()
assert deck.db.scalar(
"select count() from fsums") == 2
def test_upgrade():
import tempfile, shutil
src = os.path.join(testDir, "support", "anki12.anki")
(fd, dst) = tempfile.mkstemp(suffix=".anki")
print "upgrade to", dst
shutil.copy(src, dst)
deck = Deck(dst)
# creation time should have been adjusted
d = datetime.datetime.fromtimestamp(deck.crt)
assert d.hour == 4 and d.minute == 0
# 3 new, 2 failed, 1 due
assert deck.sched.counts() == (3,2,1)
# now's a good time to test the integrity check too
deck.fixIntegrity()
def test_groups():
deck = getEmptyDeck()
# we start with a standard group
assert len(deck.groups) == 1
# it should have an id of 1
assert deck.groups['1']
# create a new group
ts = deck.groupID("new group")
assert ts
assert len(deck.groups) == 2
# should get the same id
assert deck.groupID("new group") == ts
# by default, everything should be shown
assert not deck.qconf['groups']
def test_selective():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u"1"; f.tags = ["one", "three"]
deck.addFact(f)
f = deck.newFact()
f['Front'] = u"2"; f.tags = ["two", "three", "four"]
deck.addFact(f)
f = deck.newFact()
f['Front'] = u"3"; f.tags = ["one", "two", "three", "four"]
deck.addFact(f)
assert len(deck.selTagFids(["one"], [])) == 2
assert len(deck.selTagFids(["three"], [])) == 3
assert len(deck.selTagFids([], ["three"])) == 0
assert len(deck.selTagFids(["one"], ["three"])) == 0
assert len(deck.selTagFids(["one"], ["two"])) == 1
assert len(deck.selTagFids(["two", "three"], [])) == 3
assert len(deck.selTagFids(["two", "three"], ["one"])) == 1
assert len(deck.selTagFids(["one", "three"], ["two", "four"])) == 1
deck.setGroupForTags(["three"], [], 3)
assert deck.db.scalar("select count() from cards where gid = 3") == 3
deck.setGroupForTags(["one"], [], 2)
assert deck.db.scalar("select count() from cards where gid = 2") == 2
def test_addDelTags():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u"1"
deck.addFact(f)
f2 = deck.newFact()
f2['Front'] = u"2"
deck.addFact(f2)
# adding for a given id
deck.addTags([f.id], "foo")
f.load(); f2.load()
assert "foo" in f.tags
assert "foo" not in f2.tags
# should be canonified
deck.addTags([f.id], "foo aaa")
f.load()
assert f.tags[0] == "aaa"
assert len(f.tags) == 2
def test_timestamps():
deck = getEmptyDeck()
assert len(deck.models.models) == 2
for i in range(100):
addBasicModel(deck)
assert len(deck.models.models) == 102