Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit d00bbf6

Browse files
authored
Merge pull request #920 from bjones1/win-fix
Add Windows code to kill a stuck server.
2 parents 9deb1f9 + 6de6837 commit d00bbf6

1 file changed

Lines changed: 58 additions & 19 deletions

File tree

runestone/unittest_base.py

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import logging
1313
import os
1414
import platform
15-
import pytest
1615
import signal
1716
import time
1817
import subprocess
@@ -25,7 +24,9 @@
2524
# -------------------
2625
from selenium import webdriver
2726
from selenium.webdriver.chrome.options import Options
27+
import pytest
2828
from pyvirtualdisplay import Display
29+
2930
logging.basicConfig(level=logging.WARN)
3031
mylogger = logging.getLogger()
3132

@@ -36,9 +37,14 @@
3637
# Select an unused port for serving web pages to the test suite.
3738
PORT = "8081"
3839
# Use the localhost for testing.
39-
HOST = "http://127.0.0.1:" + PORT
40+
HOST_ADDRESS = "127.0.0.1:" + PORT
41+
HOST_URL = "http://" + HOST_ADDRESS
4042

4143

44+
# Define the platform.
45+
IS_WINDOWS = platform.system() == "Windows"
46+
IS_LINUX = sys.platform.startswith("linux")
47+
4248
# Provide access to the currently-active ModuleFixture object.
4349
mf = None
4450

@@ -57,7 +63,7 @@ def __init__(
5763
self.base_path = os.path.dirname(module_path)
5864
self.exit_status_success = exit_status_success
5965
# Windows Compatability
60-
if platform.system() is "Windows" and self.base_path is "":
66+
if IS_WINDOWS and self.base_path == "":
6167
self.base_path = "."
6268

6369
def setUpModule(self):
@@ -75,29 +81,62 @@ def setUpModule(self):
7581
print(self.build_stdout_data + self.build_stderr_data)
7682
if self.exit_status_success:
7783
self.assertFalse(p.returncode)
78-
# Make sure any older servers on port 8081 are killed -- Windows???
79-
if sys.platform in ("darwin", "linux"):
80-
mylogger.debug("Checking for stale Runestone processes")
81-
process = subprocess.Popen(["lsof", "-i", ":{0}".format(PORT)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
82-
stdout, stderr = process.communicate()
83-
for process in str(stdout.decode("utf-8")).split("\n")[1:]:
84-
data = [x for x in process.split(" ") if x != '']
85-
if (len(data) <= 1):
84+
# Make sure any older servers on port 8081 are killed.
85+
if IS_WINDOWS:
86+
netstat_output = subprocess.run(
87+
# Flags are:
88+
#
89+
# -n: Display addresses numerically. Looking up names is slow.
90+
# -o: Include the PID for each connection.
91+
["netstat", "-no"],
92+
universal_newlines=True,
93+
stdout=subprocess.PIPE,
94+
).stdout
95+
# Skip the first four lines, which are headings.
96+
for connection in netstat_output.splitlines()[4:]:
97+
# Typical output is:
98+
## Proto Local Address Foreign Address State PID
99+
## TCP 127.0.0.1:1277 127.0.0.1:49971 ESTABLISHED 4624
100+
proto, local_address, foreign_address, state, pid = connection.split()
101+
pid = int(pid)
102+
if local_address == HOST_ADDRESS and pid != 0:
103+
os.kill(pid, 0)
104+
else:
105+
lsof_output = subprocess.run(
106+
["lsof", "-i", ":{0}".format(PORT)],
107+
universal_newlines=True,
108+
stdout=subprocess.PIPE,
109+
).stdout
110+
for process in lsof_output.split("\n")[1:]:
111+
data = [x for x in process.split(" ") if x != ""]
112+
if len(data) <= 1:
86113
continue
87114
ptokill = int(data[1])
88-
mylogger.warn("Attempting to kill a stale runestone serve process: {}".format(ptokill))
115+
mylogger.warn(
116+
"Attempting to kill a stale runestone serve process: {}".format(
117+
ptokill
118+
)
119+
)
89120
os.kill(ptokill, signal.SIGKILL)
90-
time.sleep(2) # give the old process a couple seconds to clear out
121+
time.sleep(2) # give the old process a couple seconds to clear out
91122
try:
92123
os.kill(ptokill, 0) # will throw an Error if process gone
93-
pytest.exit("Stale runestone server can't kill process: {}".format(ptokill))
124+
pytest.exit(
125+
"Stale runestone server can't kill process: {}".format(ptokill)
126+
)
94127
except ProcessLookupError:
95128
# The process was killed
96129
pass
97130
except PermissionError:
98-
pytest.exit("Another server is using port {} process: {}".format(PORT, ptokill))
131+
pytest.exit(
132+
"Another server is using port {} process: {}".format(
133+
PORT, ptokill
134+
)
135+
)
99136
except Exception:
100-
pytest.exit("Unknown error while trying to kill stale runestone server")
137+
pytest.exit(
138+
"Unknown error while trying to kill stale runestone server"
139+
)
101140

102141
# Run the server. Simply calling ``runestone serve`` fails, since the process killed isn't the actual server, but probably a setuptools-created launcher.
103142
self.runestone_server = subprocess.Popen(
@@ -107,7 +146,7 @@ def setUpModule(self):
107146
# Testing time in dominated by browser startup/shutdown. So, simply run all tests in a module in a single browser instance to speed things up. See ``RunestoneTestCase.setUp`` for additional code to (mostly) clear the browser between tests.
108147
#
109148
# `PyVirtualDisplay <http://pyvirtualdisplay.readthedocs.io/en/latest/>`_ only runs on X-windows, meaning Linux. Mac seems to have `some support <https://support.apple.com/en-us/HT201341>`_. Windows is out of the question.
110-
if sys.platform.startswith("linux"):
149+
if IS_LINUX:
111150
self.display = Display(visible=0, size=(1280, 1024))
112151
self.display.start()
113152
else:
@@ -125,7 +164,7 @@ def setUpModule(self):
125164
# Wait for the webserver to come up.
126165
for tries in range(50):
127166
try:
128-
urlopen(HOST, timeout=5)
167+
urlopen(HOST_URL, timeout=5)
129168
except URLError:
130169
# Wait for the server to come up.
131170
time.sleep(0.1)
@@ -180,7 +219,7 @@ class RunestoneTestCase(unittest.TestCase):
180219
def setUp(self):
181220
# Use the shared module-wide driver.
182221
self.driver = mf.driver
183-
self.host = HOST
222+
self.host = HOST_URL
184223

185224
def tearDown(self):
186225
# Clear as much as possible, to present an almost-fresh instance of a browser for the next test. (Shutting down then starting up a browswer is very slow.)

0 commit comments

Comments
 (0)