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

Commit ff6bcfd

Browse files
committed
Fix: Port Linux/Mac code to Windows.
Clean: Tidy up subprocess calls.
1 parent 04bcb0f commit ff6bcfd

1 file changed

Lines changed: 51 additions & 17 deletions

File tree

runestone/unittest_base.py

Lines changed: 51 additions & 17 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,6 +24,7 @@
2524
# -------------------
2625
from selenium import webdriver
2726
from selenium.webdriver.chrome.options import Options
27+
import pytest
2828
from pyvirtualdisplay import Display
2929
logging.basicConfig(level=logging.WARN)
3030
mylogger = logging.getLogger()
@@ -36,9 +36,14 @@
3636
# Select an unused port for serving web pages to the test suite.
3737
PORT = "8081"
3838
# Use the localhost for testing.
39-
HOST = "http://127.0.0.1:" + PORT
39+
HOST_ADDRESS = "127.0.0.1:" + PORT
40+
HOST_URL = "http://" + HOST_ADDRESS
4041

4142

43+
# Define the platform.
44+
IS_WINDOWS = platform.system() == "Windows"
45+
IS_LINUX = sys.platform.startswith("linux")
46+
4247
# Provide access to the currently-active ModuleFixture object.
4348
mf = None
4449

@@ -57,7 +62,7 @@ def __init__(
5762
self.base_path = os.path.dirname(module_path)
5863
self.exit_status_success = exit_status_success
5964
# Windows Compatability
60-
if platform.system() == "Windows" and self.base_path == "":
65+
if IS_WINDOWS and self.base_path == "":
6166
self.base_path = "."
6267

6368
def setUpModule(self):
@@ -75,29 +80,58 @@ def setUpModule(self):
7580
print(self.build_stdout_data + self.build_stderr_data)
7681
if self.exit_status_success:
7782
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):
83+
# Make sure any older servers on port 8081 are killed.
84+
if IS_WINDOWS:
85+
netstat_output = subprocess.run(
86+
# Flags are:
87+
#
88+
# -n: Display addresses numerically. Looking up names is slow.
89+
# -o: Include the PID for each connection.
90+
["netstat", "-no"],
91+
universal_newlines=True,
92+
stdout=subprocess.PIPE,
93+
).stdout
94+
# Skip the first four lines, which are headings.
95+
for connection in netstat_output.splitlines()[4:]:
96+
# Typical output is:
97+
## Proto Local Address Foreign Address State PID
98+
## TCP 127.0.0.1:1277 127.0.0.1:49971 ESTABLISHED 4624
99+
proto, local_address, foreign_address, state, pid = connection.split()
100+
pid = int(pid)
101+
if local_address == HOST_ADDRESS and pid != 0:
102+
os.kill(pid, 0)
103+
else:
104+
lsof_output = subprocess.run(
105+
["lsof", "-i", ":{0}".format(PORT)],
106+
universal_newlines=True,
107+
stdout=subprocess.PIPE,
108+
).stdout
109+
for process in lsof_output.split("\n")[1:]:
110+
data = [x for x in process.split(" ") if x != ""]
111+
if len(data) <= 1:
86112
continue
87113
ptokill = int(data[1])
88114
mylogger.warn("Attempting to kill a stale runestone serve process: {}".format(ptokill))
89115
os.kill(ptokill, signal.SIGKILL)
90116
time.sleep(2) # give the old process a couple seconds to clear out
91117
try:
92118
os.kill(ptokill, 0) # will throw an Error if process gone
93-
pytest.exit("Stale runestone server can't kill process: {}".format(ptokill))
119+
pytest.exit(
120+
"Stale runestone server can't kill process: {}".format(ptokill)
121+
)
94122
except ProcessLookupError:
95123
# The process was killed
96124
pass
97125
except PermissionError:
98-
pytest.exit("Another server is using port {} process: {}".format(PORT, ptokill))
126+
pytest.exit(
127+
"Another server is using port {} process: {}".format(
128+
PORT, ptokill
129+
)
130+
)
99131
except Exception:
100-
pytest.exit("Unknown error while trying to kill stale runestone server")
132+
pytest.exit(
133+
"Unknown error while trying to kill stale runestone server"
134+
)
101135

102136
# Run the server. Simply calling ``runestone serve`` fails, since the process killed isn't the actual server, but probably a setuptools-created launcher.
103137
self.runestone_server = subprocess.Popen(
@@ -107,7 +141,7 @@ def setUpModule(self):
107141
# 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.
108142
#
109143
# `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"):
144+
if IS_LINUX:
111145
self.display = Display(visible=0, size=(1280, 1024))
112146
self.display.start()
113147
else:
@@ -125,7 +159,7 @@ def setUpModule(self):
125159
# Wait for the webserver to come up.
126160
for tries in range(50):
127161
try:
128-
urlopen(HOST, timeout=5)
162+
urlopen(HOST_URL, timeout=5)
129163
except URLError:
130164
# Wait for the server to come up.
131165
time.sleep(0.1)
@@ -180,7 +214,7 @@ class RunestoneTestCase(unittest.TestCase):
180214
def setUp(self):
181215
# Use the shared module-wide driver.
182216
self.driver = mf.driver
183-
self.host = HOST
217+
self.host = HOST_URL
184218

185219
def tearDown(self):
186220
# 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)