anki/pylib/tests/test_schedv2.py

1308 lines
34 KiB
Python
Raw Normal View History

# coding: utf-8
import copy
import time
from anki import hooks
2020-02-02 10:19:45 +01:00
from anki.consts import *
from anki.utils import intTime
from tests.shared import getEmptyCol as getEmptyColOrig
def getEmptyCol():
col = getEmptyColOrig()
col.changeSchedulerVer(2)
return col
# Between 2-4AM, shift the time back so test assumptions hold.
lt = time.localtime()
if lt.tm_hour >= 2 and lt.tm_hour < 4:
orig_time = time.time
2019-12-25 05:18:34 +01:00
def adjusted_time():
2019-12-25 05:18:34 +01:00
return orig_time() - 60 * 60 * 2
time.time = adjusted_time
2019-12-25 05:18:34 +01:00
2013-10-01 21:04:06 +02:00
def test_clock():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
2019-12-25 05:18:34 +01:00
if (d.sched.dayCutoff - intTime()) < 10 * 60:
2013-10-01 21:04:06 +02:00
raise Exception("Unit tests will fail around the day rollover.")
2019-12-25 05:18:34 +01:00
2013-01-17 02:39:04 +01:00
def checkRevIvl(d, c, targetIvl):
2012-12-22 04:36:51 +01:00
min, max = d.sched._fuzzIvlRange(targetIvl)
2013-01-17 02:39:04 +01:00
return min <= c.ivl <= max
2012-12-22 04:36:51 +01:00
2019-12-25 05:18:34 +01:00
def test_basics():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
d.reset()
assert not d.sched.getCard()
2019-12-25 05:18:34 +01:00
def test_new():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
d.reset()
assert d.sched.newCount == 0
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
d.reset()
assert d.sched.newCount == 1
# fetch it
c = d.sched.getCard()
assert c
2020-02-02 10:19:45 +01:00
assert c.queue == QUEUE_TYPE_NEW
assert c.type == CARD_TYPE_NEW
# if we answer it, it should become a learn card
t = intTime()
d.sched.answerCard(c, 1)
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
assert c.type == CARD_TYPE_LRN
assert c.due >= t
2014-06-03 11:17:02 +02:00
# disabled for now, as the learn fudging makes this randomly fail
# # the default order should ensure siblings are not seen together, and
# # should show all cards
# m = d.models.current(); mm = d.models
# t = mm.newTemplate("Reverse")
# t['qfmt'] = "{{Back}}"
# t['afmt'] = "{{Front}}"
# mm.addTemplate(m, t)
# mm.save(m)
# f = d.newNote()
# f['Front'] = u"2"; f['Back'] = u"2"
# d.addNote(f)
# f = d.newNote()
# f['Front'] = u"3"; f['Back'] = u"3"
# d.addNote(f)
# d.reset()
# qs = ("2", "3", "2", "3")
# for n in range(4):
# c = d.sched.getCard()
# assert qs[n] in c.q()
# d.sched.answerCard(c, 2)
2019-12-25 05:18:34 +01:00
def test_newLimits():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add some notes
g2 = d.decks.id("Default::foo")
for i in range(30):
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = str(i)
if i > 4:
2019-12-25 05:18:34 +01:00
f.model()["did"] = g2
d.addNote(f)
# give the child deck a different configuration
c2 = d.decks.confId("new conf")
d.decks.setConf(d.decks.get(g2), c2)
d.reset()
# both confs have defaulted to a limit of 20
assert d.sched.newCount == 20
# first card we get comes from parent
c = d.sched.getCard()
assert c.did == 1
# limit the parent to 10 cards, meaning we get 10 in total
conf1 = d.decks.confForDid(1)
2019-12-25 05:18:34 +01:00
conf1["new"]["perDay"] = 10
d.reset()
assert d.sched.newCount == 10
# if we limit child to 4, we should get 9
conf2 = d.decks.confForDid(g2)
2019-12-25 05:18:34 +01:00
conf2["new"]["perDay"] = 4
d.reset()
assert d.sched.newCount == 9
2019-12-25 05:18:34 +01:00
def test_newBoxes():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
d.reset()
c = d.sched.getCard()
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["new"]["delays"] = [1, 2, 3, 4, 5]
d.sched.answerCard(c, 2)
# should handle gracefully
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["new"]["delays"] = [1]
d.sched.answerCard(c, 2)
2019-12-25 05:18:34 +01:00
def test_learn():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
f = d.addNote(f)
# set as a learn card and rebuild queues
d.db.execute("update cards set queue=0, type=0")
d.reset()
# sched.getCard should return it, since it's due in the past
c = d.sched.getCard()
assert c
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["new"]["delays"] = [0.5, 3, 10]
# fail it
d.sched.answerCard(c, 1)
# it should have three reps left to graduation
2019-12-25 05:18:34 +01:00
assert c.left % 1000 == 3
assert c.left // 1000 == 3
# it should by due in 30 seconds
t = round(c.due - time.time())
assert t >= 25 and t <= 40
# pass it once
d.sched.answerCard(c, 3)
# it should by due in 3 minutes
dueIn = c.due - time.time()
2019-12-25 05:18:34 +01:00
assert 179 <= dueIn <= 180 * 1.25
assert c.left % 1000 == 2
assert c.left // 1000 == 2
# check log is accurate
log = d.db.first("select * from revlog order by id desc")
assert log[3] == 3
assert log[4] == -180
assert log[5] == -30
# pass again
d.sched.answerCard(c, 3)
# it should by due in 10 minutes
dueIn = c.due - time.time()
2019-12-25 05:18:34 +01:00
assert 599 <= dueIn <= 600 * 1.25
assert c.left % 1000 == 1
assert c.left // 1000 == 1
# the next pass should graduate the card
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
assert c.type == CARD_TYPE_LRN
d.sched.answerCard(c, 3)
2020-02-02 10:40:30 +01:00
assert c.queue == QUEUE_TYPE_REV
assert c.type == CARD_TYPE_REV
# should be due tomorrow, with an interval of 1
2019-12-25 05:18:34 +01:00
assert c.due == d.sched.today + 1
assert c.ivl == 1
# or normal removal
c.type = 0
c.queue = 1
d.sched.answerCard(c, 4)
2020-02-02 10:40:30 +01:00
assert c.type == CARD_TYPE_REV
assert c.queue == QUEUE_TYPE_REV
2013-01-17 02:39:04 +01:00
assert checkRevIvl(d, c, 4)
# revlog should have been updated each time
assert d.db.scalar("select count() from revlog where type = 0") == 5
2019-12-25 05:18:34 +01:00
def test_relearn():
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
c.ivl = 100
c.due = d.sched.today
2020-02-02 10:40:30 +01:00
c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
c.flush()
# fail the card
d.reset()
c = d.sched.getCard()
d.sched.answerCard(c, 1)
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
assert c.type == 3
assert c.ivl == 1
# immediately graduate it
d.sched.answerCard(c, 4)
2020-02-02 10:40:30 +01:00
assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
2019-12-09 04:00:15 +01:00
assert c.ivl == 2
assert c.due == d.sched.today + c.ivl
2019-12-25 05:18:34 +01:00
2018-02-02 05:30:53 +01:00
def test_relearn_no_steps():
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
2018-02-02 05:30:53 +01:00
d.addNote(f)
c = f.cards()[0]
c.ivl = 100
c.due = d.sched.today
2020-02-02 10:40:30 +01:00
c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
2018-02-02 05:30:53 +01:00
c.flush()
conf = d.decks.confForDid(1)
2019-12-25 05:18:34 +01:00
conf["lapse"]["delays"] = []
2018-02-02 05:30:53 +01:00
d.decks.save(conf)
# fail the card
d.reset()
c = d.sched.getCard()
d.sched.answerCard(c, 1)
2020-02-02 10:40:30 +01:00
assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
2018-02-02 05:30:53 +01:00
2019-12-25 05:18:34 +01:00
def test_learn_collapsed():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add 2 notes
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "1"
f = d.addNote(f)
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "2"
f = d.addNote(f)
# set as a learn card and rebuild queues
d.db.execute("update cards set queue=0, type=0")
d.reset()
# should get '1' first
c = d.sched.getCard()
assert c.q().endswith("1")
# pass it so it's due in 10 minutes
d.sched.answerCard(c, 3)
# get the other card
c = d.sched.getCard()
assert c.q().endswith("2")
# fail it so it's due in 1 minute
d.sched.answerCard(c, 1)
# we shouldn't get the same card again
c = d.sched.getCard()
assert not c.q().endswith("2")
2019-12-25 05:18:34 +01:00
def test_learn_day():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f = d.addNote(f)
d.sched.reset()
c = d.sched.getCard()
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["new"]["delays"] = [1, 10, 1440, 2880]
# pass it
d.sched.answerCard(c, 3)
# two reps to graduate, 1 more today
2019-12-25 05:18:34 +01:00
assert c.left % 1000 == 3
assert c.left // 1000 == 1
assert d.sched.counts() == (0, 1, 0)
c = d.sched.getCard()
ni = d.sched.nextIvl
assert ni(c, 3) == 86400
# answering it will place it in queue 3
d.sched.answerCard(c, 3)
2019-12-25 05:18:34 +01:00
assert c.due == d.sched.today + 1
assert c.queue == 3
assert not d.sched.getCard()
# for testing, move it back a day
c.due -= 1
c.flush()
d.reset()
assert d.sched.counts() == (0, 1, 0)
c = d.sched.getCard()
# nextIvl should work
2019-12-25 05:18:34 +01:00
assert ni(c, 3) == 86400 * 2
# if we fail it, it should be back in the correct queue
d.sched.answerCard(c, 1)
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
d.undo()
d.reset()
c = d.sched.getCard()
d.sched.answerCard(c, 3)
# simulate the passing of another two days
c.due -= 2
c.flush()
d.reset()
# the last pass should graduate it into a review card
assert ni(c, 3) == 86400
d.sched.answerCard(c, 3)
2020-02-02 10:40:30 +01:00
assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
# if the lapse step is tomorrow, failing it should handle the counts
# correctly
c.due = 0
c.flush()
d.reset()
assert d.sched.counts() == (0, 0, 1)
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["lapse"]["delays"] = [1440]
c = d.sched.getCard()
d.sched.answerCard(c, 1)
assert c.queue == 3
assert d.sched.counts() == (0, 0, 0)
2019-12-25 05:18:34 +01:00
def test_reviews():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
# set the card up as a review card, due 8 days ago
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
c.due = d.sched.today - 8
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
c.reps = 3
c.lapses = 1
c.ivl = 100
c.startTimer()
c.flush()
# save it for later use as well
cardcopy = copy.copy(c)
# try with an ease of 2
##################################################
c = copy.copy(cardcopy)
c.flush()
d.reset()
d.sched.answerCard(c, 2)
2020-02-02 10:40:30 +01:00
assert c.queue == QUEUE_TYPE_REV
# the new interval should be (100) * 1.2 = 120
assert checkRevIvl(d, c, 120)
2012-12-22 04:36:51 +01:00
assert c.due == d.sched.today + c.ivl
# factor should have been decremented
assert c.factor == 2350
# check counters
assert c.lapses == 1
assert c.reps == 4
# ease 3
##################################################
c = copy.copy(cardcopy)
c.flush()
d.sched.answerCard(c, 3)
# the new interval should be (100 + 8/2) * 2.5 = 260
2013-01-17 02:39:04 +01:00
assert checkRevIvl(d, c, 260)
2012-12-22 04:36:51 +01:00
assert c.due == d.sched.today + c.ivl
# factor should have been left alone
2017-02-08 08:28:05 +01:00
assert c.factor == STARTING_FACTOR
# ease 4
##################################################
c = copy.copy(cardcopy)
c.flush()
d.sched.answerCard(c, 4)
# the new interval should be (100 + 8) * 2.5 * 1.3 = 351
2013-01-17 02:39:04 +01:00
assert checkRevIvl(d, c, 351)
2012-12-22 04:36:51 +01:00
assert c.due == d.sched.today + c.ivl
# factor should have been increased
assert c.factor == 2650
# leech handling
##################################################
c = copy.copy(cardcopy)
c.lapses = 7
c.flush()
# steup hook
hooked = []
2019-12-25 05:18:34 +01:00
def onLeech(card):
hooked.append(1)
2019-12-25 05:18:34 +01:00
2020-01-15 07:53:24 +01:00
hooks.card_did_leech.append(onLeech)
d.sched.answerCard(c, 1)
assert hooked
2020-01-31 04:48:14 +01:00
assert c.queue == QUEUE_TYPE_SUSPENDED
c.load()
2020-01-31 04:48:14 +01:00
assert c.queue == QUEUE_TYPE_SUSPENDED
2019-12-25 05:18:34 +01:00
def test_review_limits():
d = getEmptyCol()
parent = d.decks.get(d.decks.id("parent"))
child = d.decks.get(d.decks.id("parent::child"))
pconf = d.decks.getConf(d.decks.confId("parentConf"))
cconf = d.decks.getConf(d.decks.confId("childConf"))
2019-12-25 05:18:34 +01:00
pconf["rev"]["perDay"] = 5
d.decks.updateConf(pconf)
2019-12-25 05:18:34 +01:00
d.decks.setConf(parent, pconf["id"])
cconf["rev"]["perDay"] = 10
d.decks.updateConf(cconf)
2019-12-25 05:18:34 +01:00
d.decks.setConf(child, cconf["id"])
m = d.models.current()
2019-12-25 05:18:34 +01:00
m["did"] = child["id"]
2019-12-20 22:25:30 +01:00
d.models.save(m, updateReqs=False)
# add some cards
for i in range(20):
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
# make them reviews
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
c.due = 0
c.flush()
tree = d.sched.deckDueTree()
# (('Default', 1, 0, 0, 0, ()), ('parent', 1514457677462, 5, 0, 0, (('child', 1514457677463, 5, 0, 0, ()),)))
2019-12-25 05:18:34 +01:00
assert tree[1][2] == 5 # parent
assert tree[1][5][0][2] == 5 # child
# .counts() should match
2019-12-25 05:18:34 +01:00
d.decks.select(child["id"])
d.sched.reset()
assert d.sched.counts() == (0, 0, 5)
# answering a card in the child should decrement parent count
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert d.sched.counts() == (0, 0, 4)
tree = d.sched.deckDueTree()
2019-12-25 05:18:34 +01:00
assert tree[1][2] == 4 # parent
assert tree[1][5][0][2] == 4 # child
# switch limits
2019-12-25 05:18:34 +01:00
d.decks.setConf(parent, cconf["id"])
d.decks.setConf(child, pconf["id"])
d.decks.select(parent["id"])
d.sched.reset()
# child limits do not affect the parent
tree = d.sched.deckDueTree()
2019-12-25 05:18:34 +01:00
assert tree[1][2] == 9 # parent
assert tree[1][5][0][2] == 4 # child
def test_button_spacing():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
# 1 day ivl review card due now
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
c.due = d.sched.today
c.reps = 1
c.ivl = 1
c.startTimer()
c.flush()
d.reset()
ni = d.sched.nextIvlStr
assert ni(c, 2) == "2 days"
assert ni(c, 3) == "3 days"
assert ni(c, 4) == "4 days"
2018-07-11 13:23:22 +02:00
# if hard factor is <= 1, then hard may not increase
conf = d.decks.confForDid(1)
2019-12-25 05:18:34 +01:00
conf["rev"]["hardFactor"] = 1
2018-07-11 13:23:22 +02:00
assert ni(c, 2) == "1 day"
2019-12-25 05:18:34 +01:00
def test_overdue_lapse():
# disabled in commit 3069729776990980f34c25be66410e947e9d51a2
return
d = getEmptyCol() # pylint: disable=unreachable
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
# simulate a review that was lapsed and is now due for its normal review
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = 1
c.due = -1
c.odue = -1
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
c.left = 2002
c.ivl = 0
c.flush()
d.sched._clearOverdue = False
# checkpoint
d.save()
d.sched.reset()
assert d.sched.counts() == (0, 2, 0)
c = d.sched.getCard()
d.sched.answerCard(c, 3)
# it should be due tomorrow
assert c.due == d.sched.today + 1
# revert to before
d.rollback()
d.sched._clearOverdue = True
# with the default settings, the overdue card should be removed from the
# learning queue
d.sched.reset()
assert d.sched.counts() == (0, 0, 1)
2019-12-25 05:18:34 +01:00
def test_finished():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# nothing due
assert "Congratulations" in d.sched.finishedMsg()
assert "limit" not in d.sched.finishedMsg()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
# have a new card
assert "new cards available" in d.sched.finishedMsg()
# turn it into a review
d.reset()
c = f.cards()[0]
c.startTimer()
d.sched.answerCard(c, 3)
# nothing should be due tomorrow, as it's due in a week
assert "Congratulations" in d.sched.finishedMsg()
assert "limit" not in d.sched.finishedMsg()
2019-12-25 05:18:34 +01:00
def test_nextIvl():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
d.reset()
conf = d.decks.confForDid(1)
2019-12-25 05:18:34 +01:00
conf["new"]["delays"] = [0.5, 3, 10]
conf["lapse"]["delays"] = [1, 5, 9]
c = d.sched.getCard()
# new cards
##################################################
ni = d.sched.nextIvl
assert ni(c, 1) == 30
2019-12-25 05:18:34 +01:00
assert ni(c, 2) == (30 + 180) // 2
assert ni(c, 3) == 180
2019-12-25 05:18:34 +01:00
assert ni(c, 4) == 4 * 86400
d.sched.answerCard(c, 1)
# cards in learning
##################################################
assert ni(c, 1) == 30
2019-12-25 05:18:34 +01:00
assert ni(c, 2) == (30 + 180) // 2
assert ni(c, 3) == 180
2019-12-25 05:18:34 +01:00
assert ni(c, 4) == 4 * 86400
d.sched.answerCard(c, 3)
assert ni(c, 1) == 30
2019-12-25 05:18:34 +01:00
assert ni(c, 2) == (180 + 600) // 2
assert ni(c, 3) == 600
2019-12-25 05:18:34 +01:00
assert ni(c, 4) == 4 * 86400
d.sched.answerCard(c, 3)
# normal graduation is tomorrow
2019-12-25 05:18:34 +01:00
assert ni(c, 3) == 1 * 86400
assert ni(c, 4) == 4 * 86400
# lapsed cards
##################################################
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.ivl = 100
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
assert ni(c, 1) == 60
2019-12-25 05:18:34 +01:00
assert ni(c, 3) == 100 * 86400
assert ni(c, 4) == 101 * 86400
# review cards
##################################################
2020-02-02 10:40:30 +01:00
c.queue = QUEUE_TYPE_REV
c.ivl = 100
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
# failing it should put it at 60s
assert ni(c, 1) == 60
# or 1 day if relearn is false
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["lapse"]["delays"] = []
assert ni(c, 1) == 1 * 86400
# (* 100 1.2 86400)10368000.0
assert ni(c, 2) == 10368000
# (* 100 2.5 86400)21600000.0
assert ni(c, 3) == 21600000
# (* 100 2.5 1.3 86400)28080000.0
assert ni(c, 4) == 28080000
assert d.sched.nextIvlStr(c, 4) == "10.8 months"
2019-12-25 05:18:34 +01:00
def test_bury():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "two"
d.addNote(f)
c2 = f.cards()[0]
# burying
d.sched.buryCards([c.id], manual=True) # pylint: disable=unexpected-keyword-arg
c.load()
2020-01-31 04:21:20 +01:00
assert c.queue == QUEUE_TYPE_MANUALLY_BURIED
d.sched.buryCards([c2.id], manual=False) # pylint: disable=unexpected-keyword-arg
c2.load()
2020-01-31 04:40:51 +01:00
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
d.reset()
assert not d.sched.getCard()
d.sched.unburyCardsForDeck(type="manual") # pylint: disable=unexpected-keyword-arg
2019-12-25 05:18:34 +01:00
c.load()
2020-02-02 10:19:45 +01:00
assert c.queue == QUEUE_TYPE_NEW
2019-12-25 05:18:34 +01:00
c2.load()
2020-01-31 04:40:51 +01:00
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
d.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg
type="siblings"
)
2019-12-25 05:18:34 +01:00
c2.load()
2020-02-02 10:19:45 +01:00
assert c2.queue == QUEUE_TYPE_NEW
d.sched.buryCards([c.id, c2.id])
d.sched.unburyCardsForDeck(type="all") # pylint: disable=unexpected-keyword-arg
d.reset()
assert d.sched.counts() == (2, 0, 0)
2019-12-25 05:18:34 +01:00
def test_suspend():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
# suspending
d.reset()
assert d.sched.getCard()
d.sched.suspendCards([c.id])
d.reset()
assert not d.sched.getCard()
# unsuspending
d.sched.unsuspendCards([c.id])
d.reset()
assert d.sched.getCard()
# should cope with rev cards being relearnt
2019-12-25 05:18:34 +01:00
c.due = 0
c.ivl = 100
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
2019-12-25 05:18:34 +01:00
c.flush()
d.reset()
c = d.sched.getCard()
d.sched.answerCard(c, 1)
assert c.due >= time.time()
due = c.due
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
assert c.type == 3
d.sched.suspendCards([c.id])
d.sched.unsuspendCards([c.id])
c.load()
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
assert c.type == 3
assert c.due == due
# should cope with cards in cram decks
c.due = 1
c.flush()
cram = d.decks.newDyn("tmp")
d.sched.rebuildDyn()
c.load()
assert c.due != 1
assert c.did != 1
d.sched.suspendCards([c.id])
c.load()
assert c.due != 1
assert c.did != 1
assert c.odue == 1
2019-12-25 05:18:34 +01:00
def test_filt_reviewing_early_normal():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
c.ivl = 100
2020-02-02 10:40:30 +01:00
c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
# due in 25 days, so it's been waiting 75 days
c.due = d.sched.today + 25
c.mod = 1
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
c.startTimer()
c.flush()
d.reset()
2019-12-25 05:18:34 +01:00
assert d.sched.counts() == (0, 0, 0)
# create a dynamic deck and refresh it
did = d.decks.newDyn("Cram")
d.sched.rebuildDyn(did)
d.reset()
2017-12-24 10:27:19 +01:00
# should appear as normal in the deck list
assert sorted(d.sched.deckDueList())[0][2] == 1
# and should appear in the counts
2019-12-25 05:18:34 +01:00
assert d.sched.counts() == (0, 0, 1)
# grab it and check estimates
c = d.sched.getCard()
assert d.sched.answerButtons(c) == 4
assert d.sched.nextIvl(c, 1) == 600
2019-12-25 05:18:34 +01:00
assert d.sched.nextIvl(c, 2) == int(75 * 1.2) * 86400
assert d.sched.nextIvl(c, 3) == int(75 * 2.5) * 86400
assert d.sched.nextIvl(c, 4) == int(75 * 2.5 * 1.15) * 86400
# answer 'good'
d.sched.answerCard(c, 3)
checkRevIvl(d, c, 90)
assert c.due == d.sched.today + c.ivl
assert not c.odue
# should not be in learning
2020-02-02 10:40:30 +01:00
assert c.queue == QUEUE_TYPE_REV
# should be logged as a cram rep
2019-12-25 05:18:34 +01:00
assert d.db.scalar("select type from revlog order by id desc limit 1") == 3
# due in 75 days, so it's been waiting 25 days
c.ivl = 100
c.due = d.sched.today + 75
c.flush()
d.sched.rebuildDyn(did)
d.reset()
c = d.sched.getCard()
2019-12-25 05:18:34 +01:00
assert d.sched.nextIvl(c, 2) == 60 * 86400
assert d.sched.nextIvl(c, 3) == 100 * 86400
assert d.sched.nextIvl(c, 4) == 114 * 86400
def test_filt_keep_lrn_state():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
# fail the card outside filtered deck
c = d.sched.getCard()
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["new"]["delays"] = [1, 10, 61]
d.decks.save()
d.sched.answerCard(c, 1)
2020-02-02 10:28:04 +01:00
assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.left == 3003
d.sched.answerCard(c, 3)
2020-02-02 10:28:04 +01:00
assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
# create a dynamic deck and refresh it
did = d.decks.newDyn("Cram")
d.sched.rebuildDyn(did)
d.reset()
# card should still be in learning state
c.load()
2020-02-02 10:28:04 +01:00
assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.left == 2002
# should be able to advance learning steps
d.sched.answerCard(c, 3)
# should be due at least an hour in the future
2019-12-25 05:18:34 +01:00
assert c.due - intTime() > 60 * 60
# emptying the deck preserves learning state
d.sched.emptyDyn(did)
c.load()
2020-02-02 10:28:04 +01:00
assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.left == 1001
2019-12-25 05:18:34 +01:00
assert c.due - intTime() > 60 * 60
def test_preview():
# add cards
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
orig = copy.copy(c)
f2 = d.newNote()
2019-12-25 05:18:34 +01:00
f2["Front"] = "two"
d.addNote(f2)
# cram deck
did = d.decks.newDyn("Cram")
cram = d.decks.get(did)
2019-12-25 05:18:34 +01:00
cram["resched"] = False
d.sched.rebuildDyn(did)
d.reset()
# grab the first card
c = d.sched.getCard()
assert d.sched.answerButtons(c) == 2
2018-01-20 06:26:11 +01:00
assert d.sched.nextIvl(c, 1) == 600
assert d.sched.nextIvl(c, 2) == 0
# failing it will push its due time back
due = c.due
d.sched.answerCard(c, 1)
assert c.due != due
# the other card should come next
c2 = d.sched.getCard()
assert c2.id != c.id
# passing it will remove it
d.sched.answerCard(c2, 2)
2020-02-02 10:19:45 +01:00
assert c2.queue == QUEUE_TYPE_NEW
assert c2.reps == 0
2020-02-02 10:19:45 +01:00
assert c2.type == CARD_TYPE_NEW
# the other card should appear again
c = d.sched.getCard()
assert c.id == orig.id
# emptying the filtered deck should restore card
d.sched.emptyDyn(did)
c.load()
2020-02-02 10:19:45 +01:00
assert c.queue == QUEUE_TYPE_NEW
assert c.reps == 0
2020-02-02 10:19:45 +01:00
assert c.type == CARD_TYPE_NEW
2019-12-25 05:18:34 +01:00
def test_ordcycle():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add two more templates and set second active
2019-12-25 05:18:34 +01:00
m = d.models.current()
mm = d.models
t = mm.newTemplate("Reverse")
2019-12-25 05:18:34 +01:00
t["qfmt"] = "{{Back}}"
t["afmt"] = "{{Front}}"
mm.addTemplate(m, t)
t = mm.newTemplate("f2")
2019-12-25 05:18:34 +01:00
t["qfmt"] = "{{Front}}"
t["afmt"] = "{{Back}}"
mm.addTemplate(m, t)
mm.save(m)
# create a new note; it should have 3 cards
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "1"
f["Back"] = "1"
d.addNote(f)
assert d.cardCount() == 3
d.reset()
# ordinals should arrive in order
assert d.sched.getCard().ord == 0
assert d.sched.getCard().ord == 1
assert d.sched.getCard().ord == 2
2019-12-25 05:18:34 +01:00
def test_counts_idx():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
d.reset()
assert d.sched.counts() == (1, 0, 0)
c = d.sched.getCard()
# counter's been decremented but idx indicates 1
assert d.sched.counts() == (0, 0, 0)
assert d.sched.countIdx(c) == 0
# answer to move to learn queue
d.sched.answerCard(c, 1)
assert d.sched.counts() == (0, 1, 0)
# fetching again will decrement the count
c = d.sched.getCard()
assert d.sched.counts() == (0, 0, 0)
assert d.sched.countIdx(c) == 1
# answering should add it back again
d.sched.answerCard(c, 1)
assert d.sched.counts() == (0, 1, 0)
2019-12-25 05:18:34 +01:00
def test_repCounts():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
d.reset()
# lrnReps should be accurate on pass/fail
assert d.sched.counts() == (1, 0, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.counts() == (0, 0, 0)
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "two"
d.addNote(f)
d.reset()
# initial pass should be correct too
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.counts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 4)
assert d.sched.counts() == (0, 0, 0)
# immediate graduate should work
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "three"
d.addNote(f)
d.reset()
d.sched.answerCard(d.sched.getCard(), 4)
assert d.sched.counts() == (0, 0, 0)
# and failing a review should too
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "three"
d.addNote(f)
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
c.due = d.sched.today
c.flush()
d.reset()
assert d.sched.counts() == (0, 0, 1)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.counts() == (0, 1, 0)
2019-12-25 05:18:34 +01:00
def test_timing():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a few review cards, due today
for i in range(5):
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "num" + str(i)
d.addNote(f)
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
c.due = 0
c.flush()
# fail the first one
d.reset()
c = d.sched.getCard()
d.sched.answerCard(c, 1)
# the next card should be another review
2017-12-24 10:27:19 +01:00
c2 = d.sched.getCard()
2020-02-02 10:40:30 +01:00
assert c2.queue == QUEUE_TYPE_REV
2017-12-24 10:27:19 +01:00
# if the failed card becomes due, it should show first
c.due = time.time() - 1
c.flush()
d.reset()
c = d.sched.getCard()
2020-02-02 10:28:04 +01:00
assert c.queue == QUEUE_TYPE_LRN
2019-12-25 05:18:34 +01:00
def test_collapse():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
d.reset()
# test collapsing
c = d.sched.getCard()
d.sched.answerCard(c, 1)
c = d.sched.getCard()
d.sched.answerCard(c, 4)
assert not d.sched.getCard()
2019-12-25 05:18:34 +01:00
def test_deckDue():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note with default deck
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
# and one that's a child
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "two"
default1 = f.model()["did"] = d.decks.id("Default::1")
d.addNote(f)
# make it a review card
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.queue = QUEUE_TYPE_REV
c.due = 0
c.flush()
# add one more with a new deck
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "two"
foobar = f.model()["did"] = d.decks.id("foo::bar")
d.addNote(f)
# and one that's a sibling
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "three"
foobaz = f.model()["did"] = d.decks.id("foo::baz")
d.addNote(f)
d.reset()
assert len(d.decks.decks) == 5
cnts = d.sched.deckDueList()
assert cnts[0] == ["Default", 1, 1, 0, 1]
assert cnts[1] == ["Default::1", default1, 1, 0, 0]
assert cnts[2] == ["foo", d.decks.id("foo"), 0, 0, 0]
assert cnts[3] == ["foo::bar", foobar, 0, 0, 1]
assert cnts[4] == ["foo::baz", foobaz, 0, 0, 1]
tree = d.sched.deckDueTree()
assert tree[0][0] == "Default"
# sum of child and parent
assert tree[0][1] == 1
assert tree[0][2] == 1
assert tree[0][4] == 1
# child count is just review
assert tree[0][5][0][0] == "1"
assert tree[0][5][0][1] == default1
assert tree[0][5][0][2] == 1
assert tree[0][5][0][4] == 0
# code should not fail if a card has an invalid deck
2019-12-25 05:18:34 +01:00
c.did = 12345
c.flush()
d.sched.deckDueList()
d.sched.deckDueTree()
2019-12-25 05:18:34 +01:00
def test_deckTree():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
d.decks.id("new::b::c")
d.decks.id("new2")
# new should not appear twice in tree
names = [x[0] for x in d.sched.deckDueTree()]
names.remove("new")
assert "new" not in names
2019-12-25 05:18:34 +01:00
def test_deckFlow():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note with default deck
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
# and one that's a child
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "two"
default1 = f.model()["did"] = d.decks.id("Default::2")
d.addNote(f)
# and another that's higher up
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "three"
default1 = f.model()["did"] = d.decks.id("Default::1")
d.addNote(f)
# should get top level one first, then ::1, then ::2
d.reset()
2019-12-25 05:18:34 +01:00
assert d.sched.counts() == (3, 0, 0)
for i in "one", "three", "two":
c = d.sched.getCard()
2019-12-25 05:18:34 +01:00
assert c.note()["Front"] == i
d.sched.answerCard(c, 3)
2019-12-25 05:18:34 +01:00
def test_reorder():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note with default deck
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
f2 = d.newNote()
2019-12-25 05:18:34 +01:00
f2["Front"] = "two"
d.addNote(f2)
assert f2.cards()[0].due == 2
2019-12-25 05:18:34 +01:00
found = False
# 50/50 chance of being reordered
for i in range(20):
d.sched.randomizeCards(1)
if f.cards()[0].due != f.id:
2019-12-25 05:18:34 +01:00
found = True
break
assert found
d.sched.orderCards(1)
assert f.cards()[0].due == 1
# shifting
f3 = d.newNote()
2019-12-25 05:18:34 +01:00
f3["Front"] = "three"
d.addNote(f3)
f4 = d.newNote()
2019-12-25 05:18:34 +01:00
f4["Front"] = "four"
d.addNote(f4)
assert f.cards()[0].due == 1
assert f2.cards()[0].due == 2
assert f3.cards()[0].due == 3
assert f4.cards()[0].due == 4
2019-12-25 05:18:34 +01:00
d.sched.sortCards([f3.cards()[0].id, f4.cards()[0].id], start=1, shift=True)
assert f.cards()[0].due == 3
assert f2.cards()[0].due == 4
assert f3.cards()[0].due == 1
assert f4.cards()[0].due == 2
2019-12-25 05:18:34 +01:00
def test_forget():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.queue = QUEUE_TYPE_REV
c.type = CARD_TYPE_REV
2019-12-25 05:18:34 +01:00
c.ivl = 100
c.due = 0
c.flush()
d.reset()
assert d.sched.counts() == (0, 0, 1)
d.sched.forgetCards([c.id])
d.reset()
assert d.sched.counts() == (1, 0, 0)
2019-12-25 05:18:34 +01:00
def test_resched():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
d.sched.reschedCards([c.id], 0, 0)
c.load()
assert c.due == d.sched.today
assert c.ivl == 1
2020-02-02 10:40:30 +01:00
assert c.queue == QUEUE_TYPE_REV and c.type == CARD_TYPE_REV
d.sched.reschedCards([c.id], 1, 1)
c.load()
2019-12-25 05:18:34 +01:00
assert c.due == d.sched.today + 1
assert c.ivl == +1
2019-12-25 05:18:34 +01:00
def test_norelearn():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
# add a note
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
d.addNote(f)
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
c.due = 0
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
c.reps = 3
c.lapses = 1
c.ivl = 100
c.startTimer()
c.flush()
d.reset()
d.sched.answerCard(c, 1)
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["lapse"]["delays"] = []
d.sched.answerCard(c, 1)
2019-12-25 05:18:34 +01:00
def test_failmult():
2014-06-03 10:38:47 +02:00
d = getEmptyCol()
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
c = f.cards()[0]
2020-02-02 10:40:30 +01:00
c.type = CARD_TYPE_REV
c.queue = QUEUE_TYPE_REV
c.ivl = 100
c.due = d.sched.today - c.ivl
2017-02-08 08:28:05 +01:00
c.factor = STARTING_FACTOR
c.reps = 3
c.lapses = 1
c.startTimer()
c.flush()
2019-12-25 05:18:34 +01:00
d.sched._cardConf(c)["lapse"]["mult"] = 0.5
c = d.sched.getCard()
d.sched.answerCard(c, 1)
assert c.ivl == 50
d.sched.answerCard(c, 1)
assert c.ivl == 25
2018-01-14 07:58:12 +01:00
2019-12-25 05:18:34 +01:00
2018-01-14 07:58:12 +01:00
def test_moveVersions():
col = getEmptyCol()
col.changeSchedulerVer(1)
2018-01-14 07:58:12 +01:00
n = col.newNote()
2019-12-25 05:18:34 +01:00
n["Front"] = "one"
2018-01-14 07:58:12 +01:00
col.addNote(n)
# make it a learning card
col.reset()
c = col.sched.getCard()
col.sched.answerCard(c, 1)
# the move to v2 should reset it to new
col.changeSchedulerVer(2)
c.load()
2020-02-02 10:19:45 +01:00
assert c.queue == QUEUE_TYPE_NEW
assert c.type == CARD_TYPE_NEW
2018-01-14 07:58:12 +01:00
# fail it again, and manually bury it
col.reset()
c = col.sched.getCard()
col.sched.answerCard(c, 1)
col.sched.buryCards([c.id])
c.load()
2020-01-31 04:21:20 +01:00
assert c.queue == QUEUE_TYPE_MANUALLY_BURIED
2018-01-14 07:58:12 +01:00
# revert to version 1
col.changeSchedulerVer(1)
# card should have moved queues
c.load()
2020-01-31 04:40:51 +01:00
assert c.queue == QUEUE_TYPE_SIBLING_BURIED
2018-01-14 07:58:12 +01:00
# and it should be new again when unburied
col.sched.unburyCards()
c.load()
2020-02-02 10:19:45 +01:00
assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW
# make sure relearning cards transition correctly to v1
col.changeSchedulerVer(2)
# card with 100 day interval, answering again
col.sched.reschedCards([c.id], 100, 100)
2019-12-25 05:18:34 +01:00
c.load()
c.due = 0
c.flush()
col.sched._cardConf(c)["lapse"]["mult"] = 0.5
col.sched.reset()
c = col.sched.getCard()
col.sched.answerCard(c, 1)
# due should be correctly set when removed from learning early
col.changeSchedulerVer(1)
c.load()
assert c.due == 50
2019-12-25 05:18:34 +01:00
# cards with a due date earlier than the collection should retain
# their due date when removed
def test_negativeDueFilter():
d = getEmptyCol()
# card due prior to collection date
f = d.newNote()
2019-12-25 05:18:34 +01:00
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
c = f.cards()[0]
c.due = -5
2020-02-02 10:40:30 +01:00
c.queue = QUEUE_TYPE_REV
c.ivl = 5
c.flush()
# into and out of filtered deck
did = d.decks.newDyn("Cram")
d.sched.rebuildDyn(did)
d.sched.emptyDyn(did)
d.reset()
c.load()
assert c.due == -5
# hard on the first step should be the average of again and good,
# and it should be logged properly
def test_initial_repeat():
d = getEmptyCol()
f = d.newNote()
f["Front"] = "one"
f["Back"] = "two"
d.addNote(f)
d.reset()
c = d.sched.getCard()
d.sched.answerCard(c, 2)
# should be due in ~ 5.5 mins
expected = time.time() + 5.5 * 60
assert expected - 10 < c.due < expected * 1.25
ivl = d.db.scalar("select ivl from revlog")
assert ivl == -5.5 * 60