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

Commit baa89c0

Browse files
committed
Clean: Remove old unittest code; combine into conftest.py.
1 parent 9d26906 commit baa89c0

5 files changed

Lines changed: 165 additions & 253 deletions

File tree

index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ Components
2626
runestone/*/toctree
2727
runestone/__init__.py
2828
runestone/__main__.py
29-
runestone/unittest_base.py
3029

3130

3231
Packaging
@@ -45,6 +44,8 @@ Misc
4544

4645
ACKNOWLEDGEMENTS
4746
.github/FUNDING.yml
47+
runestone/conftest.py
48+
runestone/shared_conftest.py
4849
.gitignore
4950
.readthedocs.yml
5051
conf.py
Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
from unittest import TestCase
2-
import codecs
3-
from selenium.webdriver.common.alert import Alert
4-
from runestone.unittest_base import module_fixture_maker, RunestoneTestCase
5-
6-
mf, setUpModule, tearDownModule = module_fixture_maker(__file__, True, False)
7-
8-
9-
class AddJsTestsError(TestCase):
10-
def test_1(self):
11-
self.assertIn(
12-
"Extension error:\nUnable to find this_file_does_not_exist.js in html_static_path.",
13-
mf.build_stderr_data,
14-
)
1+
def test_1(selenium_module_fixture):
2+
assert(
3+
"Extension error:\nUnable to find this_file_does_not_exist.js in html_static_path." in
4+
selenium_module_fixture.build_stderr_data,
5+
)

runestone/conftest.py

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,44 @@
1212
#
1313
# Standard library
1414
# ----------------
15+
import logging
16+
import os
17+
import platform
18+
import signal
19+
import time
1520
import subprocess
16-
21+
import sys
22+
import unittest
23+
from urllib.request import urlopen
24+
from urllib.error import URLError
1725

1826
# Third-party imports
1927
# -------------------
2028
import pytest
21-
29+
from pyvirtualdisplay import Display
30+
from selenium import webdriver
31+
from selenium.webdriver.chrome.options import Options
2232

2333
# Local imports
2434
# -------------
2535
# This is necessary to bring in the shared pytest fixture.
2636
from runestone.shared_conftest import _SeleniumUtils, selenium_driver # noqa: F401
27-
from runestone.unittest_base import ModuleFixture, HOST_URL, IS_WINDOWS
37+
logging.basicConfig(level=logging.WARN)
38+
39+
40+
# Globals
41+
# =======
42+
# Select an unused port for serving web pages to the test suite.
43+
PORT = "8081"
44+
# Use the localhost for testing.
45+
HOST_ADDRESS = "127.0.0.1:" + PORT
46+
HOST_URL = "http://" + HOST_ADDRESS
47+
48+
# Define the platform.
49+
IS_WINDOWS = platform.system() == "Windows"
50+
IS_LINUX = sys.platform.startswith("linux")
51+
52+
mylogger = logging.getLogger()
2853

2954

3055
# Fixtures
@@ -68,3 +93,132 @@ def selenium_utils(selenium_driver): # noqa: F811
6893
def selenium_utils_get(selenium_utils):
6994
selenium_utils.get("index.html")
7095
return selenium_utils
96+
97+
98+
# Utility class
99+
# =============
100+
# Define a class to build the test Runestone project, run the server, then shut it down when the tests complete.
101+
class ModuleFixture(unittest.TestCase):
102+
def __init__(
103+
self,
104+
# The path to the Python module in which the test resides. This provides a simple way to determine the path in which to run runestone build/serve.
105+
module_path,
106+
# True if the sphinx-build process must exit with status of 0 (success)
107+
exit_status_success=True,
108+
):
109+
110+
super(ModuleFixture, self).__init__()
111+
self.base_path = os.path.dirname(module_path)
112+
self.exit_status_success = exit_status_success
113+
# Windows Compatability
114+
if IS_WINDOWS and self.base_path == "":
115+
self.base_path = "."
116+
117+
def setUpModule(self):
118+
# Change to this directory for running Runestone.
119+
self.old_cwd = os.getcwd()
120+
os.chdir(self.base_path)
121+
# Compile the docs. Save the stdout and stderr for examination.
122+
p = subprocess.run(
123+
["runestone", "build", "--all"], capture_output=True, text=True,
124+
)
125+
self.build_stdout_data = p.stdout
126+
self.build_stderr_data = p.stderr
127+
print(self.build_stdout_data + self.build_stderr_data)
128+
if self.exit_status_success:
129+
self.assertFalse(p.returncode)
130+
# Make sure any older servers on port 8081 are killed.
131+
if IS_WINDOWS:
132+
netstat_output = subprocess.run(
133+
# Flags are:
134+
#
135+
# -n: Display addresses numerically. Looking up names is slow.
136+
# -o: Include the PID for each connection.
137+
["netstat", "-no"],
138+
capture_output=True,
139+
text=True,
140+
).stdout
141+
# Skip the first four lines, which are headings.
142+
for connection in netstat_output.splitlines()[4:]:
143+
# Typical output is:
144+
## Proto Local Address Foreign Address State PID
145+
## TCP 127.0.0.1:1277 127.0.0.1:49971 ESTABLISHED 4624
146+
proto, local_address, foreign_address, state, pid = connection.split()
147+
pid = int(pid)
148+
if local_address == HOST_ADDRESS and pid != 0:
149+
os.kill(pid, 0)
150+
else:
151+
lsof_output = subprocess.run(
152+
["lsof", "-i", ":{0}".format(PORT)], capture_output=True, text=True,
153+
).stdout
154+
for process in lsof_output.split("\n")[1:]:
155+
data = [x for x in process.split(" ") if x != ""]
156+
if len(data) <= 1:
157+
continue
158+
ptokill = int(data[1])
159+
mylogger.warn(
160+
"Attempting to kill a stale runestone serve process: {}".format(
161+
ptokill
162+
)
163+
)
164+
os.kill(ptokill, signal.SIGKILL)
165+
time.sleep(2) # give the old process a couple seconds to clear out
166+
try:
167+
os.kill(ptokill, 0) # will throw an Error if process gone
168+
pytest.exit(
169+
"Stale runestone server can't kill process: {}".format(ptokill)
170+
)
171+
except ProcessLookupError:
172+
# The process was killed
173+
pass
174+
except PermissionError:
175+
pytest.exit(
176+
"Another server is using port {} process: {}".format(
177+
PORT, ptokill
178+
)
179+
)
180+
except Exception:
181+
pytest.exit(
182+
"Unknown error while trying to kill stale runestone server"
183+
)
184+
185+
# Run the server. Simply calling ``runestone serve`` fails, since the process killed isn't the actual server, but probably a setuptools-created launcher.
186+
self.runestone_server = subprocess.Popen(
187+
[sys.executable, "-m", "runestone", "serve", "--port", PORT]
188+
)
189+
190+
# 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.
191+
#
192+
# `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.
193+
if IS_LINUX:
194+
self.display = Display(visible=0, size=(1280, 1024))
195+
self.display.start()
196+
else:
197+
self.display = None
198+
# self.driver = webdriver.PhantomJS() # use this for Jenkins auto testing
199+
options = Options()
200+
options.add_argument("--window-size=1200,800")
201+
options.add_argument("--no-sandbox")
202+
self.driver = webdriver.Chrome(options=options) # good for development.
203+
204+
# Wait for the webserver to come up.
205+
for tries in range(50):
206+
try:
207+
urlopen(HOST_URL, timeout=5)
208+
except URLError:
209+
# Wait for the server to come up.
210+
time.sleep(0.1)
211+
else:
212+
# The server is up. We're done.
213+
break
214+
215+
def tearDownModule(self):
216+
# Shut down Selenium.
217+
self.driver.close()
218+
self.driver.quit()
219+
if self.display:
220+
self.display.stop()
221+
# Shut down the server.
222+
self.runestone_server.kill()
223+
# Restore the directory.
224+
os.chdir(self.old_cwd)

runestone/mchoice/test/test_assess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
__author__ = "yasinovskyy"
66

7-
from runestone.unittest_base import (
7+
from runestone.shared_conftest import (
88
element_has_css_class,
99
)
1010
from selenium.webdriver.common.by import By

0 commit comments

Comments
 (0)