diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_res.py | 57 | ||||
| -rw-r--r-- | tests/test_res_io.py | 7 |
2 files changed, 64 insertions, 0 deletions
diff --git a/tests/test_res.py b/tests/test_res.py index 6c92549..e4dfb1c 100644 --- a/tests/test_res.py +++ b/tests/test_res.py @@ -385,6 +385,63 @@ class Queue(unittest.TestCase): led = R.Ledger(entries=[queued("r-1", 20)]) self.assertIsNone(R.queue_estimate(R.Config(), led, facts(), led.get("r-1"))) +class BatchLoan(unittest.TestCase): + """Real case: batch r-741 claims 6.3 GB, its gate r-757 (10 GB) queued, budget 7.6 → must not deadlock.""" + + def setUp(self): + self.batch = running("r-741", title="wf batch", mem=6.3, cpus=1) + # 21.9 − 6 reserve − 2 headroom − 6.3 unused claim = 7.6 GB budget + self.f = facts(available=21.9, units={"wf-r-741.service": R.Unit(True, 0.0)}) + + def gate(self, id="r-757", batch="r-741", mem=10.0, at=T(13, 0)): + e = queued(id, mem, at=at) + e.by = {"batch": batch} if batch else {} + return e + + def test_budget_is_real_case(self): + led = R.Ledger(entries=[self.batch]) + self.assertAlmostEqual(R.budget(R.Config(), led, self.f)[0], 7.6) + + def test_own_gate_borrows_batch_claim(self): + led = R.Ledger(entries=[self.batch, self.gate()]) + self.assertEqual([e.id for e in R.to_start(R.Config(), led, self.f)], ["r-757"]) + self.assertIsNotNone(R.queue_estimate(R.Config(), led, self.f, led.get("r-757"))) + + def test_foreign_job_does_not_borrow(self): + led = R.Ledger(entries=[self.batch, self.gate(batch="")]) + self.assertEqual(R.to_start(R.Config(), led, self.f), []) + led = R.Ledger(entries=[self.batch, self.gate(batch="r-1")]) # other batch, not live + self.assertEqual(R.to_start(R.Config(), led, self.f), []) + + def test_claim_lent_once(self): + # 7.6 + 6.3 = 13.9: first 10 GB gate fits, second does not (loan used up, 3.9 left) + led = R.Ledger(entries=[self.batch, self.gate("r-757"), self.gate("r-758", at=T(13, 1))]) + self.assertEqual([e.id for e in R.to_start(R.Config(), led, self.f)], ["r-757"]) + led.get("r-757").state = "running" + led.get("r-757").unit = "wf-r-757.service" + self.f.units["wf-r-757.service"] = R.Unit(True, 0.0) + self.assertEqual(R.loan(led, led.get("r-758")), (0.0, 0)) + self.assertEqual(R.to_start(R.Config(), led, self.f), []) + + def test_dead_batch_gate_dropped(self): + # r-740: gate of dead batch r-731 heads the queue; r-757 behind it must still start + dead = self.gate("r-740", batch="r-731", at=T(12, 0)) + led = R.Ledger(entries=[self.batch, dead, self.gate()]) + self.assertEqual(R.prune(led, self.f), ["r-740 dropped (batch r-731 gone)"]) + self.assertEqual((dead.state, dead.why), ("done", "owner gone")) + self.assertEqual([e.id for e in R.to_start(R.Config(), led, self.f)], ["r-757"]) + + def test_batch_ending_in_same_prune_drops_gate(self): + led = R.Ledger(entries=[self.batch, self.gate()]) + f = facts(units={"wf-r-741.service": R.Unit(False)}) + self.assertEqual(R.prune(led, f), ["r-741 exited rc=?", "r-757 dropped (batch r-741 gone)"]) + + def test_live_batch_gate_kept(self): + led = R.Ledger(entries=[self.batch, self.gate()]) + self.assertEqual(R.prune(led, self.f), []) + self.assertEqual(led.get("r-757").state, "queued") + + class Game(unittest.TestCase): def setUp(self): self.cfg = R.Config() diff --git a/tests/test_res_io.py b/tests/test_res_io.py index d91046a..16271e0 100644 --- a/tests/test_res_io.py +++ b/tests/test_res_io.py @@ -426,6 +426,13 @@ class QueueIO(IOBase): self.assertEqual((e.state, e.unit), ("running", "wf-r-2.service")) self.assertEqual(self.box.fake.ran("systemd-run")[-1][-2:], ["sh", "y"]) + def test_batch_gate_borrows_batch_claim(self): + self.box.wf("run", "--mem", "10G", "--for", "40m", "--title", "wf batch", "--", "x") + self.box.caller["WF_RES_ID"] = "r-1" # the gate is queued from inside batch r-1 + code, out, _ = self.box.wf("run", "--mem", "8G", "--for", "10m", "--title", "gate", "--queue", "--", "y") + self.assertEqual((code, out.split(";")[0]), (0, "r-2 started")) + self.assertEqual(self.box.ledger().get("r-2").by["batch"], "r-1") + def test_direct_run_does_not_jump_queue(self): self.box.wf("run", "--mem", "10G", "--for", "40m", "--title", "big", "--", "x") self.box.wf("run", "--mem", "8G", "--for", "10m", "--title", "two", "--queue", "--", "y") |
