Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH Actions: Run tests on windows #2567

Merged
merged 13 commits into from
Jan 28, 2024
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
include:
#- {name: Windows, python: '3.9', os: windows-latest, tox: py39}
- {name: Windows, python: '3.12', os: windows-latest, tox: fail_fast_test_main}
#- {name: Mac, python: '3.9', os: macos-latest, tox: py39}
- { name: "ruff", python: "3.11", os: ubuntu-latest, tox: "ruff" }
- { name: "mypy", python: "3.10", os: ubuntu-latest, tox: "mypy" }
Expand Down
2 changes: 1 addition & 1 deletion locust/argument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def find_locustfiles(locustfiles: list[str], is_directory: bool) -> list[str]:
for root, dirs, files in os.walk(locustdir):
for file in files:
if not file.startswith("_") and file.lower() != "locust.py" and file.endswith(".py"):
file_path = f"{root}/{file}"
file_path = os.path.join(root, file)
file_paths.append(file_path)
else:
for file_path in locustfiles:
Expand Down
14 changes: 9 additions & 5 deletions locust/input_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import gevent

if os.name == "nt":
import pywintypes
from win32api import STD_INPUT_HANDLE
from win32console import (
ENABLE_ECHO_INPUT,
Expand Down Expand Up @@ -48,11 +49,14 @@ def poll(_self):
class WindowsKeyPoller:
def __init__(self):
if sys.stdin.isatty():
self.read_handle = GetStdHandle(STD_INPUT_HANDLE)
self.read_handle.SetConsoleMode(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT)
self.cur_event_length = 0
self.cur_keys_length = 0
self.captured_chars = []
try:
self.read_handle = GetStdHandle(STD_INPUT_HANDLE)
self.read_handle.SetConsoleMode(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT)
self.cur_event_length = 0
self.cur_keys_length = 0
self.captured_chars = []
except pywintypes.error:
raise InitError("Terminal says its a tty but we couldnt enable line input. Keyboard input disabled.")
else:
raise InitError("Terminal was not a tty. Keyboard input disabled")

Expand Down
32 changes: 27 additions & 5 deletions locust/test/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import json
import os
import platform
import pty
import signal
import socket
import subprocess
Expand Down Expand Up @@ -79,6 +78,7 @@ def test_help_arg(self):
self.assertIn("Logging options:", output)
self.assertIn("--skip-log-setup Disable Locust's logging setup.", output)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_custom_arguments(self):
port = get_free_tcp_port()
with temporary_file(
Expand Down Expand Up @@ -120,6 +120,7 @@ def my_task(self):
self.assertNotIn("command_line_value", stdout)
self.assertIn("web_form_value", stdout)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_custom_arguments_in_file(self):
with temporary_file(
content=textwrap.dedent(
Expand Down Expand Up @@ -155,6 +156,7 @@ def my_task(self):
self.assertIn("Starting Locust", stderr)
self.assertIn("config_file_value", stdout)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_custom_exit_code(self):
with temporary_file(
content=textwrap.dedent(
Expand Down Expand Up @@ -184,6 +186,7 @@ def my_task(self):
self.assertIn("Exit code in quit event 42", stdout)
self.assertEqual(42, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_webserver(self):
with temporary_file(
content=textwrap.dedent(
Expand Down Expand Up @@ -284,6 +287,7 @@ def my_task(self):
self.assertIn("parameter need to be float and value between. 0 < percentile < 1 Eg 0.95", stderr)
self.assertEqual(1, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_webserver_multiple_locustfiles(self):
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_A) as mocked1:
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_B) as mocked2:
Expand All @@ -299,6 +303,7 @@ def test_webserver_multiple_locustfiles(self):
self.assertIn("Shutting down (exit code 0)", stderr)
self.assertEqual(0, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_webserver_multiple_locustfiles_in_directory(self):
with TemporaryDirectory() as temp_dir:
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_A, dir=temp_dir):
Expand All @@ -313,6 +318,7 @@ def test_webserver_multiple_locustfiles_in_directory(self):
self.assertIn("Shutting down (exit code 0)", stderr)
self.assertEqual(0, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_webserver_multiple_locustfiles_with_shape(self):
content = textwrap.dedent(
"""
Expand Down Expand Up @@ -406,6 +412,7 @@ def test_invalid_stop_timeout_string(self):
self.assertIn("ERROR/locust.main: Valid --stop-timeout formats are", stderr)
self.assertEqual(1, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_headless_spawn_options_wo_run_time(self):
with mock_locustfile() as mocked:
proc = subprocess.Popen(
Expand All @@ -431,6 +438,7 @@ def test_headless_spawn_options_wo_run_time(self):
self.assertIn("Shutting down (exit code 0)", stderr)
self.assertEqual(0, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_run_headless_with_multiple_locustfiles(self):
with TemporaryDirectory() as temp_dir:
with mock_locustfile(dir=temp_dir):
Expand Down Expand Up @@ -582,6 +590,7 @@ def my_task(self):
self.assertIn("Shutting down (exit code 0)", stderr)
self.assertEqual(0, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_autostart_wo_run_time(self):
port = get_free_tcp_port()
with mock_locustfile() as mocked:
Expand All @@ -600,7 +609,7 @@ def test_autostart_wo_run_time(self):
)
gevent.sleep(1.9)
try:
response = requests.get(f"http://0.0.0.0:{port}/")
response = requests.get(f"http://localhost:{port}/")
except Exception:
pass
self.assertEqual(200, response.status_code)
Expand Down Expand Up @@ -636,7 +645,7 @@ def test_autostart_w_run_time(self):
)
gevent.sleep(1.9)
try:
response = requests.get(f"http://0.0.0.0:{port}/")
response = requests.get(f"http://localhost:{port}/")
except Exception:
pass
_, stderr = proc.communicate(timeout=2)
Expand All @@ -648,6 +657,7 @@ def test_autostart_w_run_time(self):
self.assertEqual(200, response.status_code)
self.assertIn('<body class="running">', response.text)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_run_autostart_with_multiple_locustfiles(self):
with TemporaryDirectory() as temp_dir:
with mock_locustfile(dir=temp_dir):
Expand Down Expand Up @@ -722,7 +732,7 @@ def tick(self):
text=True,
)
gevent.sleep(1.9)
response = requests.get(f"http://0.0.0.0:{port}/")
response = requests.get(f"http://localhost:{port}/")
try:
success = True
_, stderr = proc.communicate(timeout=5)
Expand Down Expand Up @@ -789,7 +799,7 @@ def my_task(self):
text=True,
)
gevent.sleep(1.9)
response = requests.get(f"http://0.0.0.0:{port}/")
response = requests.get(f"http://localhost:{port}/")
try:
success = True
_, stderr = proc.communicate(timeout=5)
Expand All @@ -807,6 +817,7 @@ def my_task(self):
self.assertIn('<body class="spawning">', response.text)
self.assertTrue(success, "got timeout and had to kill the process")

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_web_options(self):
port = get_free_tcp_port()
if platform.system() == "Darwin":
Expand Down Expand Up @@ -842,7 +853,10 @@ def test_web_options(self):
self.assertEqual(200, requests.get("http://127.0.0.1:%i/" % port, timeout=1).status_code)
proc.terminate()

@unittest.skipIf(os.name == "nt", reason="termios doesnt exist on windows, adn thus we cannot import pty")
def test_input(self):
import pty

LOCUSTFILE_CONTENT = textwrap.dedent(
"""
from locust import User, TaskSet, task, between
Expand Down Expand Up @@ -1310,6 +1324,7 @@ def task1(self):
self.assertIn("No tasks defined on MyUser", stderr)
self.assertEqual(1, proc.returncode)

@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
def test_graceful_exit_when_keyboard_interrupt(self):
with temporary_file(
content=textwrap.dedent(
Expand Down Expand Up @@ -1780,6 +1795,7 @@ def my_task(self):
if found[i] != i:
raise Exception(f"expected index {i} but got", found[i])

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_processes(self):
with mock_locustfile() as mocked:
command = f"locust -f {mocked.file_path} --processes 4 --headless --run-time 1 --exit-code-on-error 0"
Expand All @@ -1799,6 +1815,7 @@ def test_processes(self):
self.assertIn("(index 3) reported as ready", stderr)
self.assertIn("Shutting down (exit code 0)", stderr)

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_processes_autodetect(self):
with mock_locustfile() as mocked:
command = f"locust -f {mocked.file_path} --processes -1 --headless --run-time 1 --exit-code-on-error 0"
Expand All @@ -1818,6 +1835,7 @@ def test_processes_autodetect(self):
self.assertIn("(index 0) reported as ready", stderr)
self.assertIn("Shutting down (exit code 0)", stderr)

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_processes_separate_worker(self):
with mock_locustfile() as mocked:
master_proc = subprocess.Popen(
Expand Down Expand Up @@ -1862,6 +1880,7 @@ def test_processes_separate_worker(self):
self.assertIn("(index 3) reported as ready", master_stderr)
self.assertIn("Shutting down (exit code 0)", master_stderr)

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_processes_ctrl_c(self):
with mock_locustfile() as mocked:
proc = psutil.Popen( # use psutil.Popen instead of subprocess.Popen to use extra features
Expand Down Expand Up @@ -1901,6 +1920,7 @@ def test_processes_ctrl_c(self):
self.assertIn("The last worker quit, stopping test", stderr)
self.assertIn("Shutting down (exit code 0)", stderr)

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_workers_shut_down_if_master_is_gone(self):
content = """
from locust import HttpUser, task, constant, runners
Expand Down Expand Up @@ -1956,6 +1976,7 @@ def my_task(self):
self.assertNotIn("Traceback", worker_stderr)
self.assertIn("Didn't get heartbeat from master in over ", worker_stderr)

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_processes_error_doesnt_blow_up_completely(self):
with mock_locustfile() as mocked:
proc = subprocess.Popen(
Expand All @@ -1979,6 +2000,7 @@ def test_processes_error_doesnt_blow_up_completely(self):
self.assertEqual(stderr.count("Unknown User(s): UserThatDoesntExist"), 5)
self.assertNotIn("Traceback", stderr)

@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
def test_processes_workers_quit_unexpected(self):
content = """
from locust import runners, events, User
Expand Down
8 changes: 4 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ allowlist_externals =
timeout
grep
commands =
python3 -m pip install .
python3 -m unittest discover []
python -m pip install .
python -m unittest discover []
; Disable these tests for now, because they are breaking. When someone has time they should be converted into regular unit tests
; bash -ec "PYTHONUNBUFFERED=1 timeout 20s python3 examples/rest.py >{temp_dir}/out.txt 2>/{temp_dir}/err.txt || true"
; grep -qm 1 'my custom error message with response text, response was {"args"' {temp_dir}/out.txt
Expand All @@ -28,8 +28,8 @@ commands =

[testenv:fail_fast_test_main]
commands =
python3 -m pip install .
python3 -m unittest -f locust.test.test_main
python -m pip install .
python -m unittest -f locust.test.test_main

[testenv:ruff]
deps = ruff==0.1.13
Expand Down
Loading