diff --git a/tests/test_single_instance.py b/tests/test_single_instance.py new file mode 100644 index 0000000..4dca977 --- /dev/null +++ b/tests/test_single_instance.py @@ -0,0 +1,82 @@ +import os +import sys +import pytest +from unittest.mock import MagicMock, patch +import wecker + +# We need to mock the PID file existence and os.kill to test the logic +# because we are running in a test environment. + +@pytest.fixture +def mock_pid_file(tmp_path): + """Provides a temporary PID file path and ensures it's cleaned up.""" + pid_file = tmp_path / "wecker.pid" + with patch("wecker.PID_FILE", str(pid_file)): + yield pid_file + +def test_ensure_single_instance_success(mock_pid_file): + """Test that the script can start if no PID file exists.""" + # Ensure file doesn't exist + if mock_pid_file.exists(): + mock_pid_file.unlink() + + with patch("os.path.exists", return_value=False), \ + patch("os.getpid", return_value=1234): + + wecker.ensure_single_instance() + + # Verify it wrote the PID + assert mock_pid_file.exists() + assert mock_pid_file.read_text() == "1234" + +def test_ensure_single_instance_already_running(mock_pid_file): + """Test that the script exits if another instance is running.""" + # 1. Create the PID file with a dummy PID + mock_pid_file.write_text("5678") + + # 2. Mock os.path.exists to find the file + # 3. Mock os.kill to succeed (meaning process 5678 is alive) + # Note: We DON'T mock builtins.open here, so it reads the real file we just wrote. + with patch("os.path.exists", return_value=True), \ + patch("os.kill") as mock_kill: + + mock_kill.return_value = None # Success means process is alive + + with pytest.raises(SystemExit) as excinfo: + wecker.ensure_single_instance() + + assert excinfo.value.code == 1 + mock_kill.assert_called_with(5678, 0) + +def test_ensure_single_instance_stale_pid(mock_pid_file): + """Test that the script continues if the PID in the file is dead.""" + mock_pid_file.write_text("5678") + + with patch("os.path.exists", return_value=True), \ + patch("os.kill", side_effect=ProcessLookupError), \ + patch("os.getpid", return_value=1234): + + wecker.ensure_single_instance() + + # Should have overwritten with new PID + assert mock_pid_file.read_text() == "1234" + +def test_remove_pid_file_success(mock_pid_file): + """Test that remove_pid_file removes the correct PID file.""" + mock_pid_file.write_text("1234") + + with patch("os.path.exists", return_value=True), \ + patch("os.getpid", return_value=1234): + + wecker.remove_pid_file() + assert not mock_pid_file.exists() + +def test_remove_pid_file_wrong_pid(mock_pid_file): + """Test that remove_pid_file does NOT remove if PID doesn't match.""" + mock_pid_file.write_text("5678") + + with patch("os.path.exists", return_value=True), \ + patch("os.getpid", return_value=1234): + + wecker.remove_pid_file() + assert mock_pid_file.exists()