Skip to content
Snippets Groups Projects
Commit ecd617b7 authored by Alan De Smet's avatar Alan De Smet
Browse files

ExclusiveLockFile now has tests

parent cf57a3a7
Branches
No related tags found
No related merge requests found
...@@ -110,6 +110,9 @@ class ExclusiveLockFile: ...@@ -110,6 +110,9 @@ class ExclusiveLockFile:
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
self.unlock() self.unlock()
def __del__(self):
self.file.close()
def samefile(f1, f2): def samefile(f1, f2):
""" Do do file objects correspond to the same file on disk? """ Do do file objects correspond to the same file on disk?
... ...
......
#! /usr/bin/python3 #! /usr/bin/python3
import csppfetch import csppfetch
import csppfetch.exclusivelockfile
import os import os
import unittest import unittest
...@@ -62,14 +63,6 @@ ELFT_MSG_WAITING = "waiting for lock" ...@@ -62,14 +63,6 @@ ELFT_MSG_WAITING = "waiting for lock"
ELFT_MSG_LOCKED = "claimed lock, waiting on pipe" ELFT_MSG_LOCKED = "claimed lock, waiting on pipe"
ELFT_MSG_UNLOCKED = "released lock" ELFT_MSG_UNLOCKED = "released lock"
ELFT_MSG_DONE = "exiting" ELFT_MSG_DONE = "exiting"
def ELFT_lock_sleep_exit(title, file, pipe, queue):
queue.put(f"{title} {ELFT_MSG_WAITING}")
from csppfetch.exclusivelockfile import ExclusiveLockFile
import time
with ExclusiveLockFile(file):
queue.put(f"{title} {ELFT_MSG_LOCKED}")
pipe.recv()
queue.put(f"{title} {ELFT_MSG_UNLOCKED}")
def ELFT_lock_proc(title, file, qresults, qcommands): def ELFT_lock_proc(title, file, qresults, qcommands):
qresults.put(f"{title} {ELFT_MSG_WAITING}") qresults.put(f"{title} {ELFT_MSG_WAITING}")
...@@ -79,8 +72,9 @@ def ELFT_lock_proc(title, file, qresults, qcommands): ...@@ -79,8 +72,9 @@ def ELFT_lock_proc(title, file, qresults, qcommands):
qresults.put(f"{title} {ELFT_MSG_LOCKED}") qresults.put(f"{title} {ELFT_MSG_LOCKED}")
cmd = qcommands.get() cmd = qcommands.get()
if cmd != "unlock": if cmd != "unlock":
qresults.put(f"{title} received {cmd} expected unlock") msg = f"{title} received '{cmd}' but expected 'unlock'"
return 1 qresults.put(msg)
raise Exception(msg)
qresults.put(f"{title} {ELFT_MSG_UNLOCKED}") qresults.put(f"{title} {ELFT_MSG_UNLOCKED}")
cmd = qcommands.get() cmd = qcommands.get()
if cmd != "done": if cmd != "done":
...@@ -88,15 +82,37 @@ def ELFT_lock_proc(title, file, qresults, qcommands): ...@@ -88,15 +82,37 @@ def ELFT_lock_proc(title, file, qresults, qcommands):
return 1 return 1
qresults.put(f"{title} {ELFT_MSG_DONE}") qresults.put(f"{title} {ELFT_MSG_DONE}")
class LockProcess:
def __init__(self, name, lockfile, process, qresults, qcommands):
self.name = name
self.lockfile = lockfile
self.process = process
self.qresults = qresults
self.qcommands = qcommands
self.running = False
def start(self):
if self.running:
raise RuntimeError("trying to start() an already running process")
self.process.start()
self.running = True
def terminate(self):
if self.running:
self.process.terminate()
self.running = False
def get(self):
return self.qresults.get()
def put(self, command):
return self.qcommands.put(command)
def is_quiet(self):
return self.qresults.empty()
class _ExclusiveLockFileTests(DTestCase): class ExclusiveLockFileTests(DTestCase):
def Xcreate_lock_process(self, name, lockfile):
from multiprocessing import Process,Pipe
parent_pipe,child_pipe = Pipe()
p = Process(target=ELFT_lock_sleep_exit,
args=[name, lockfile, child_pipe])
return(p,parent_pipe)
def create_lock_process(self, name, lockfile): def create_lock_process(self, name, lockfile):
from multiprocessing import Process, Queue from multiprocessing import Process, Queue
...@@ -104,111 +120,63 @@ class _ExclusiveLockFileTests(DTestCase): ...@@ -104,111 +120,63 @@ class _ExclusiveLockFileTests(DTestCase):
qcommands = Queue() qcommands = Queue()
p = Process(target=ELFT_lock_proc, p = Process(target=ELFT_lock_proc,
args=[name, lockfile, qresults, qcommands]) args=[name, lockfile, qresults, qcommands])
return(p,qresults,qcommands) return LockProcess(name, lockfile, p, qresults, qcommands)
def Xtest_exclusivelockfile(self):
from tempfile import TemporaryDirectory
from multiprocessing import Queue
# Testing multi-process locking code is awful.
with TemporaryDirectory() as dir:
p1,p2,p3 = [None]*3
try:
queue = Queue()
lockfile = dir+"/lock"
p1,p1_pipe = self.create_lock_process("p1", lockfile, queue)
p2,p2_pipe = self.create_lock_process("p2", lockfile, queue)
p3,p3_pipe = self.create_lock_process("p3", lockfile, queue)
p1.start()
self.assertEqual(queue.get(), f"p1 {ELFT_MSG_WAITING}")
self.assertEqual(queue.get(), f"p1 {ELFT_MSG_LOCKED}")
self.assertTrue(queue.empty())
p2.start()
self.assertEqual(queue.get(), f"p2 {ELFT_MSG_WAITING}")
self.assertTrue(queue.empty())
p1_pipe.send("go")
more = sorted([queue.get(),queue.get()])
self.assertEqual(more, [f"p1 {ELFT_MSG_UNLOCKED}", f"p2 {ELFT_MSG_LOCKED}"])
p1.join()
p1 = None
# When we get "p1 {ELFT_MSG_UNLOCKED}", the lockfile should be unlinked
# What happened with p3 shows up?
p3.start()
self.assertEqual(queue.get(), f"p3 {ELFT_MSG_WAITING}")
#print("1",queue.get())
#print("2",queue.get())
#print("3",queue.get())
self.assertTrue(queue.empty())
p2_pipe.send("go")
more = sorted([queue.get(),queue.get()])
self.assertEqual(more, [f"p2 {ELFT_MSG_UNLOCKED}", f"p3 {ELFT_MSG_LOCKED}"])
p2.join()
p2 = None
p3_pipe.send("go")
self.assertEqual(queue.get(), f"p3 {ELFT_MSG_UNLOCKED}")
p3.join()
p3 = None
self.assertTrue(queue.empty())
finally:
if p1 is not None: p1.terminate()
if p2 is not None: p2.terminate()
if p3 is not None: p3.terminate()
def test_exclusivelockfile(self): def test_exclusivelockfile(self):
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
# Trying to deterministically test non-deterministic code is awful # Trying to deterministically test non-deterministic code is awful
with TemporaryDirectory() as dir: with TemporaryDirectory() as dir:
p1,p2,p3 = [None]*3 p = []
lockfile = dir+"/lock"
open(lockfile,"x").close()
try: try:
lockfile = dir+"/lock" lockfile = dir+"/lock"
p1,p1_res,p1_cmd = self.create_lock_process("p1", lockfile) p = [self.create_lock_process(f"p{i}", lockfile) for i in range(3)]
p2,p2_res,p2_cmd = self.create_lock_process("p2", lockfile)
p3,p3_res,p3_cmd = self.create_lock_process("p3", lockfile) p[0].start()
p1.start() self.assertEqual(p[0].get(), f"p0 {ELFT_MSG_WAITING}")
self.assertEqual(p1_res.get(), f"p1 {ELFT_MSG_WAITING}") self.assertEqual(p[0].get(), f"p0 {ELFT_MSG_LOCKED}")
self.assertEqual(p1_res.get(), f"p1 {ELFT_MSG_LOCKED}") self.assertTrue(p[0].is_quiet())
self.assertTrue(p1_res.empty()) p[1].start()
p2.start() self.assertEqual(p[1].get(), f"p1 {ELFT_MSG_WAITING}")
self.assertEqual(p2_res.get(), f"p2 {ELFT_MSG_WAITING}") self.assertTrue(p[0].is_quiet())
self.assertTrue(p2_res.empty()) self.assertTrue(p[1].is_quiet())
self.assertTrue(p1_res.empty()) p[0].put("unlock")
p1_cmd.put("unlock") self.assertEqual(p[0].get(), f"p0 {ELFT_MSG_UNLOCKED}")
self.assertEqual(p1_res.get(), f"p1 {ELFT_MSG_UNLOCKED}") self.assertTrue(p[0].is_quiet())
self.assertEqual(p2_res.get(), f"p2 {ELFT_MSG_LOCKED}") self.assertEqual(p[1].get(), f"p1 {ELFT_MSG_LOCKED}")
self.assertTrue(p2_res.empty()) self.assertTrue(p[1].is_quiet())
self.assertTrue(p1_res.empty()) p[0].put("done")
p1_cmd.put("done") self.assertEqual(p[0].get(), f"p0 {ELFT_MSG_DONE}")
self.assertEqual(p1_res.get(), f"p1 {ELFT_MSG_DONE}") self.assertTrue(p[0].is_quiet())
self.assertTrue(p2_res.empty()) self.assertTrue(p[1].is_quiet())
self.assertTrue(p1_res.empty()) p[1].put("unlock")
self.assertEqual(p[1].get(), f"p1 {ELFT_MSG_UNLOCKED}")
self.assertTrue(False) self.assertTrue(p[1].is_quiet())
p[1].put("done")
more = sorted([queue.get(),queue.get()]) self.assertEqual(p[1].get(), f"p1 {ELFT_MSG_DONE}")
self.assertEqual(more, [f"p1 {ELFT_MSG_UNLOCKED}", f"p2 {ELFT_MSG_LOCKED}"]) self.assertTrue(p[1].is_quiet())
p1.join()
p1 = None # The other locks are gone, this should be trivial
# When we get "p1 {ELFT_MSG_UNLOCKED}", the lockfile should be unlinked p[2].start()
# What happened with p3 shows up? self.assertEqual(p[2].get(), f"p2 {ELFT_MSG_WAITING}")
p3.start() self.assertEqual(p[2].get(), f"p2 {ELFT_MSG_LOCKED}")
self.assertEqual(queue.get(), f"p3 {ELFT_MSG_WAITING}") self.assertTrue(p[2].is_quiet())
#print("1",queue.get()) p[2].put("unlock")
#print("2",queue.get()) self.assertEqual(p[2].get(), f"p2 {ELFT_MSG_UNLOCKED}")
#print("3",queue.get()) self.assertTrue(p[2].is_quiet())
self.assertTrue(queue.empty()) p[2].put("done")
p2_pipe.send("go") self.assertEqual(p[2].get(), f"p2 {ELFT_MSG_DONE}")
more = sorted([queue.get(),queue.get()]) self.assertTrue(p[2].is_quiet())
self.assertEqual(more, [f"p2 {ELFT_MSG_UNLOCKED}", f"p3 {ELFT_MSG_LOCKED}"])
p2.join() self.assertTrue(p[0].is_quiet())
p2 = None self.assertTrue(p[1].is_quiet())
p3_pipe.send("go") self.assertTrue(p[2].is_quiet())
self.assertEqual(queue.get(), f"p3 {ELFT_MSG_UNLOCKED}")
p3.join()
p3 = None
self.assertTrue(queue.empty())
finally: finally:
if p1 is not None: p1.terminate() for i in p:
if p2 is not None: p2.terminate() i.terminate()
if p3 is not None: p3.terminate()
... ...
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment