mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 10:41:31 +00:00
Bug: b/455433899 Change-Id: Icdc8f0fb516ed9782f85a935d733b9f26a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/7366313 Commit-Queue: Alex Ovsienko <ovsienko@google.com> Reviewed-by: Junji Watanabe <jwata@google.com>
1060 lines
40 KiB
Python
Executable File
1060 lines
40 KiB
Python
Executable File
#!/usr/bin/env vpython3
|
|
# Copyright (c) 2024 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import io
|
|
import os
|
|
import shlex
|
|
import sys
|
|
import pytest
|
|
import subprocess
|
|
import itertools
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Tuple, Generator, Optional
|
|
|
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, ROOT_DIR)
|
|
import siso
|
|
|
|
# These are required for fixtures to work.
|
|
# pylint: disable=redefined-outer-name,unused-argument
|
|
|
|
|
|
@pytest.fixture
|
|
def siso_test_fixture(tmp_path: Path,
|
|
mocker: Any) -> Generator[None, None, None]:
|
|
# Replace trial dir functionality with tmp_parth.
|
|
previous_dir = os.getcwd()
|
|
os.chdir(tmp_path)
|
|
mocker.patch("siso.getpass.getuser", return_value="testuser")
|
|
yield
|
|
os.chdir(previous_dir)
|
|
|
|
|
|
def test_load_sisorc_no_file(siso_test_fixture: Any) -> None:
|
|
global_flags, subcmd_flags = siso.load_sisorc(
|
|
os.path.join("build", "config", "siso", ".sisorc"))
|
|
assert global_flags == []
|
|
assert subcmd_flags == {}
|
|
|
|
|
|
def test_load_sisorc(siso_test_fixture: Any) -> None:
|
|
sisorc = os.path.join("build", "config", "siso", ".sisorc")
|
|
os.makedirs(os.path.dirname(sisorc))
|
|
with open(sisorc, "w") as f:
|
|
f.write("""
|
|
# comment
|
|
-credential_helper=gcloud
|
|
ninja --failure_verbose=false -k=0
|
|
""")
|
|
global_flags, subcmd_flags = siso.load_sisorc(sisorc)
|
|
assert global_flags == ["-credential_helper=gcloud"]
|
|
assert subcmd_flags == {"ninja": ["--failure_verbose=false", "-k=0"]}
|
|
|
|
|
|
def test_apply_sisorc_none(siso_test_fixture: Any) -> None:
|
|
new_args = siso.apply_sisorc([], {}, ["ninja", "-C", "out/Default"],
|
|
"ninja")
|
|
assert new_args == ["ninja", "-C", "out/Default"]
|
|
|
|
|
|
def test_apply_sisorc_nosubcmd(siso_test_fixture: Any) -> None:
|
|
new_args = siso.apply_sisorc([], {"ninja": ["-k=0"]}, ["-version"], "")
|
|
assert new_args == ["-version"]
|
|
|
|
|
|
def test_apply_sisorc(siso_test_fixture: Any) -> None:
|
|
new_args = siso.apply_sisorc(
|
|
["-credential_helper=luci-auth"], {"ninja": ["-k=0"]},
|
|
["-log_dir=/tmp", "ninja", "-C", "out/Default"], "ninja")
|
|
assert new_args == [
|
|
"-credential_helper=luci-auth", "-log_dir=/tmp", "ninja", "-k=0", "-C",
|
|
"out/Default"
|
|
]
|
|
|
|
|
|
def test_is_subcommand_present(mocker: Any) -> None:
|
|
mock_call = mocker.patch("siso.subprocess.call")
|
|
|
|
def side_effect(cmd, *_, **__):
|
|
if cmd[2] in ["collector", "ninja"]:
|
|
return 0
|
|
return 2
|
|
|
|
mock_call.side_effect = side_effect
|
|
assert siso._is_subcommand_present("siso_path", "collector")
|
|
assert siso._is_subcommand_present("siso_path", "ninja")
|
|
assert not siso._is_subcommand_present("siso_path", "unknown")
|
|
|
|
|
|
@pytest.mark.parametrize("args, want", [
|
|
pytest.param(["ninja", "-C", "out/Default"], [
|
|
"ninja", "-C", "out/Default", "--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}"
|
|
],
|
|
id="no_labels"),
|
|
pytest.param(["ninja", "-C", "out/Default", "--metrics_labels=foo=bar"],
|
|
["ninja", "-C", "out/Default", "--metrics_labels=foo=bar"],
|
|
id="labels_exist"),
|
|
])
|
|
def test_apply_metrics_labels(args: List[str], want: List[str]) -> None:
|
|
got = siso.apply_metrics_labels(args)
|
|
assert got == want
|
|
|
|
|
|
@pytest.mark.parametrize("args, env, want", [
|
|
pytest.param(["ninja", "-C", "out/Default"], {},
|
|
["ninja", "-C", "out/Default"],
|
|
id="no_env_flags"),
|
|
pytest.param([
|
|
"ninja", "-C", "out/Default", "--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler"
|
|
], {}, [
|
|
"ninja", "-C", "out/Default", "--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler"
|
|
],
|
|
id="some_already_applied_no_env_flags"),
|
|
pytest.param([
|
|
"ninja", "-C", "out/Default", "--metrics_project", "some_project"
|
|
], {}, [
|
|
"ninja", "-C", "out/Default", "--metrics_project", "some_project",
|
|
"--enable_cloud_monitoring", "--enable_cloud_profiler",
|
|
"--enable_cloud_trace", "--enable_cloud_logging", "--enable_collector"
|
|
],
|
|
id="metrics_project_set"),
|
|
pytest.param(["ninja", "-C", "out/Default"],
|
|
{"RBE_metrics_project": "some_project"}, [
|
|
"ninja", "-C", "out/Default", "--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler", "--enable_cloud_trace",
|
|
"--enable_cloud_logging", "--enable_collector"
|
|
],
|
|
id="metrics_project_set_thru_env"),
|
|
pytest.param(["ninja", "-C", "out/Default", "--project", "some_project"],
|
|
{}, [
|
|
"ninja", "-C", "out/Default", "--project", "some_project",
|
|
"--enable_cloud_monitoring", "--enable_cloud_profiler",
|
|
"--enable_cloud_trace", "--enable_cloud_logging",
|
|
"--enable_collector", "--metrics_project=some_project"
|
|
],
|
|
id="cloud_project_set"),
|
|
pytest.param(["ninja", "-C", "out/Default"],
|
|
{"SISO_PROJECT": "some_project"}, [
|
|
"ninja", "-C", "out/Default", "--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler", "--enable_cloud_trace",
|
|
"--enable_cloud_logging", "--enable_collector",
|
|
"--metrics_project=some_project"
|
|
],
|
|
id="cloud_project_set_thru_env"),
|
|
pytest.param(
|
|
["ninja", "-C", "out/Default", "--enable_cloud_profiler=false"],
|
|
{"SISO_PROJECT": "some_project"}, [
|
|
"ninja", "-C", "out/Default", "--enable_cloud_profiler=false",
|
|
"--enable_cloud_monitoring", "--enable_cloud_trace",
|
|
"--enable_cloud_logging", "--enable_collector",
|
|
"--metrics_project=some_project"
|
|
],
|
|
id="respects_set_flags"),
|
|
])
|
|
def test_apply_telemetry_flags(args: List[str], env: Dict[str, str],
|
|
want: List[str]) -> None:
|
|
got = siso.apply_telemetry_flags(args, env)
|
|
assert got == want
|
|
|
|
|
|
def test_apply_telemetry_flags_sets_expected_env_var(mocker: Any) -> None:
|
|
mocker.patch.dict("os.environ", {})
|
|
args = [
|
|
"ninja",
|
|
"-C",
|
|
"out/Default",
|
|
]
|
|
env = {}
|
|
_ = siso.apply_telemetry_flags(args, env)
|
|
assert env.get("GOOGLE_API_USE_CLIENT_CERTIFICATE") == "false"
|
|
|
|
|
|
@pytest.mark.parametrize("args, env, want", [
|
|
pytest.param(
|
|
["--metrics_project", "proj1"], {}, "proj1", id="metrics_project_arg"),
|
|
pytest.param(["--project", "proj2"], {}, "proj2", id="project_arg"),
|
|
pytest.param(["--metrics_project", "proj1", "--project", "proj2"], {},
|
|
"proj1",
|
|
id="metrics_project_and_project_args"),
|
|
pytest.param([], {"RBE_metrics_project": "proj3"},
|
|
"proj3",
|
|
id="rbe_metrics_project_env"),
|
|
pytest.param([], {"SISO_PROJECT": "proj4"}, "proj4", id="siso_project_env"),
|
|
pytest.param([], {
|
|
"RBE_metrics_project": "proj3",
|
|
"SISO_PROJECT": "proj4"
|
|
},
|
|
"proj3",
|
|
id="rbe_and_siso_project_env"),
|
|
pytest.param(["--project", "proj2"], {"RBE_metrics_project": "proj3"},
|
|
"proj2",
|
|
id="project_arg_and_rbe_env"),
|
|
pytest.param(["--metrics_project", "proj1"],
|
|
{"RBE_metrics_project": "proj3"},
|
|
"proj1",
|
|
id="metrics_project_arg_and_rbe_env"),
|
|
pytest.param([], {}, "", id="no_project"),
|
|
pytest.param(["-metrics_project", "proj1"], {},
|
|
"proj1",
|
|
id="short_metrics_project_arg"),
|
|
pytest.param(["-project", "proj2"], {}, "proj2", id="short_project_arg"),
|
|
])
|
|
def test_fetch_metrics_project(args: List[str], env: Dict[str, str],
|
|
want: str) -> None:
|
|
got = siso._fetch_metrics_project(args, env)
|
|
assert got == want
|
|
|
|
|
|
@pytest.mark.parametrize("platform, env_vars, want_path_template", [
|
|
("Linux", {
|
|
"XDG_RUNTIME_DIR": os.path.join("{root_dir}", "run", "user", "1000")
|
|
}, os.path.join("{root_dir}", "run", "user", "1000", "{user}", "siso")),
|
|
("Linux", {}, os.path.join("/tmp", "{user}", "siso")),
|
|
("Darwin", {
|
|
"TMPDIR": os.path.join("{root_dir}", "var", "folders", "12", "345..."),
|
|
},
|
|
os.path.join("{root_dir}", "var", "folders", "12", "345...", "{user}",
|
|
"siso")),
|
|
("Darwin", {}, os.path.join("/tmp", "{user}", "siso")),
|
|
("Linux", {
|
|
"XDG_RUNTIME_DIR": "a" * 100
|
|
}, os.path.join("/tmp", "{user}", "siso")),
|
|
])
|
|
def test_resolve_sockets_folder(siso_test_fixture: Any, tmp_path: Path,
|
|
platform: str, env_vars: Dict[str, str],
|
|
want_path_template: str, mocker: Any) -> None:
|
|
user = "testuser"
|
|
# Replace placeholders in paths
|
|
for key, value in env_vars.items():
|
|
env_vars[key] = value.format(root_dir=str(tmp_path))
|
|
want_path = want_path_template.format(root_dir=str(tmp_path), user=user)
|
|
|
|
mocker.patch("sys.platform", new=platform.lower())
|
|
path, length = siso._resolve_sockets_folder(env_vars)
|
|
|
|
# If the desired path is too long, the function will fall back to /tmp/<user>/siso
|
|
if (104 - len(want_path) - 6) < 1:
|
|
expected_path = os.path.join("/tmp", user, "siso")
|
|
else:
|
|
expected_path = want_path
|
|
|
|
expected_len = 104 - len(expected_path) - 6
|
|
|
|
# Windows.
|
|
assert path == expected_path
|
|
assert length == expected_len
|
|
assert os.path.isdir(path)
|
|
|
|
|
|
def test_handle_collector_args_disabled(mocker: Any) -> None:
|
|
mock_fetch = mocker.patch("siso._fetch_metrics_project",
|
|
return_value="test-project")
|
|
mock_start_collector = mocker.patch("siso._start_collector")
|
|
mocker.patch("sys.platform", new="linux")
|
|
|
|
siso_path = "path/to/siso"
|
|
out_dir = "out/Default"
|
|
env = {"SISO_PROJECT": "test-project"}
|
|
args = ["ninja", "-C", out_dir]
|
|
|
|
result = siso._handle_collector_args(siso_path, args, env)
|
|
|
|
assert result == args
|
|
mock_fetch.assert_not_called()
|
|
mock_start_collector.assert_not_called()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32",
|
|
reason="Skipping Linux-specific test on Windows")
|
|
def test_handle_collector_args_starts_linux(siso_test_fixture: Any,
|
|
mocker: Any) -> None:
|
|
mock_start_collector = mocker.patch("siso._start_collector",
|
|
return_value=True)
|
|
mocker.patch("sys.platform", new="linux")
|
|
|
|
siso_path = "path/to/siso"
|
|
env = {"SISO_PROJECT": "test-project", "XDG_RUNTIME_DIR": "/tmp/run"}
|
|
args = ["ninja", "--enable_collector"]
|
|
|
|
captured_args = []
|
|
|
|
def fetch_metrics_project_side_effect(args, env):
|
|
captured_args.append(list(args))
|
|
return "test-project"
|
|
|
|
mocker.patch("siso._fetch_metrics_project",
|
|
side_effect=fetch_metrics_project_side_effect)
|
|
result = siso._handle_collector_args(siso_path, args, env)
|
|
|
|
sockets_file = os.path.join("/tmp", "run", "testuser", "siso",
|
|
"test-project.sock")
|
|
assert result == [
|
|
"ninja", "--enable_collector",
|
|
f"--collector_address=unix://{sockets_file}"
|
|
]
|
|
assert captured_args == [["ninja", "--enable_collector"]]
|
|
mock_start_collector.assert_called_once_with(siso_path, sockets_file,
|
|
"test-project")
|
|
|
|
|
|
def test_handle_collector_args_starts_windows(mocker: Any) -> None:
|
|
mock_start_collector = mocker.patch("siso._start_collector",
|
|
return_value=True)
|
|
mock_fetch = mocker.patch("siso._fetch_metrics_project",
|
|
return_value="test-project")
|
|
mocker.patch("sys.platform", new="win32")
|
|
siso_path = "path/to/siso"
|
|
env = {"SISO_PROJECT": "test-project"}
|
|
args = ["ninja", "--enable_collector"]
|
|
original_args = list(args)
|
|
|
|
result = siso._handle_collector_args(siso_path, args, env)
|
|
|
|
assert result == ["ninja", "--enable_collector"]
|
|
mock_fetch.assert_called_once_with(original_args, env)
|
|
mock_start_collector.assert_called_once_with(siso_path, None,
|
|
"test-project")
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32",
|
|
reason="Skipping Linux-specific test on Windows")
|
|
def test_handle_collector_args_fails(siso_test_fixture: Any,
|
|
mocker: Any) -> None:
|
|
mock_start_collector = mocker.patch("siso._start_collector",
|
|
return_value=False)
|
|
mocker.patch("sys.platform", new="linux")
|
|
|
|
siso_path = "path/to/siso"
|
|
env = {"SISO_PROJECT": "test-project", "XDG_RUNTIME_DIR": "/tmp/run"}
|
|
args = ["ninja", "--enable_collector"]
|
|
|
|
captured_args = []
|
|
|
|
def fetch_metrics_project_side_effect(args, env):
|
|
captured_args.append(list(args))
|
|
return "test-project"
|
|
|
|
mocker.patch("siso._fetch_metrics_project",
|
|
side_effect=fetch_metrics_project_side_effect)
|
|
result = siso._handle_collector_args(siso_path, args, env)
|
|
|
|
assert result == ["ninja", "--enable_collector=false"]
|
|
assert captured_args == [["ninja", "--enable_collector"]]
|
|
sockets_file = os.path.join("/tmp", "run", "testuser", "siso",
|
|
"test-project.sock")
|
|
mock_start_collector.assert_called_once_with(siso_path, sockets_file,
|
|
"test-project")
|
|
|
|
|
|
@pytest.fixture
|
|
def start_collector_mocks(mocker: Any) -> Dict[str, Any]:
|
|
mocks = {
|
|
"is_subcommand_present":
|
|
mocker.patch("siso._is_subcommand_present", return_value=True),
|
|
"subprocess_run":
|
|
mocker.patch("siso.subprocess.run"),
|
|
"kill_collector":
|
|
mocker.patch("siso._kill_collector"),
|
|
"time_sleep":
|
|
mocker.patch("siso.time.sleep"),
|
|
"time_time":
|
|
mocker.patch("siso.time.time",
|
|
side_effect=(1000 + i * 0.1 for i in range(100))),
|
|
"http_connection":
|
|
mocker.patch("siso.http.client.HTTPConnection"),
|
|
"subprocess_popen":
|
|
mocker.patch("siso.subprocess.Popen"),
|
|
}
|
|
|
|
mock_conn = mocker.Mock()
|
|
mocks["http_connection"].return_value = mock_conn
|
|
mocks["mock_conn"] = mock_conn
|
|
|
|
return mocks
|
|
|
|
|
|
def _configure_http_responses(
|
|
mocker: Any,
|
|
mock_conn: Any,
|
|
status_responses: List[Tuple[int, Any]],
|
|
config_responses: Optional[List[Tuple[int, Any]]] = None) -> None:
|
|
if config_responses is None:
|
|
config_responses = []
|
|
|
|
request_path_history = []
|
|
|
|
def request_side_effect(method, path):
|
|
request_path_history.append(path)
|
|
|
|
def getresponse_side_effect():
|
|
path = request_path_history[-1]
|
|
if path == "/health/status":
|
|
if not status_responses:
|
|
return mocker.Mock(status=404,
|
|
read=mocker.Mock(return_value=b""))
|
|
status_code, _ = status_responses.pop(0)
|
|
return mocker.Mock(status=status_code,
|
|
read=mocker.Mock(return_value=b""))
|
|
if path == "/health/config":
|
|
if not config_responses:
|
|
return mocker.Mock(status=200,
|
|
read=mocker.Mock(return_value=b"{}"))
|
|
status_code, _ = config_responses.pop(0)
|
|
return mocker.Mock(status=status_code,
|
|
read=mocker.Mock(return_value=b""))
|
|
return mocker.Mock(status=404)
|
|
|
|
mock_conn.request.side_effect = request_side_effect
|
|
mock_conn.getresponse.side_effect = getresponse_side_effect
|
|
|
|
|
|
def test_start_collector_removes_existing_socket_file(
|
|
start_collector_mocks: Dict[str, Any], mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
mock_os_path_exists = mocker.patch("os.path.exists", return_value=True)
|
|
mock_os_remove = mocker.patch("os.remove")
|
|
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
sockets_file = os.path.join("/tmp", "test.sock")
|
|
_configure_http_responses(mocker,
|
|
start_collector_mocks["mock_conn"],
|
|
status_responses=[(404, None), (200, None)],
|
|
config_responses=[(200, None), (200, None)])
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config = {
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": sockets_file
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mocker.patch("siso.json.loads",
|
|
side_effect=[status_healthy, config, config])
|
|
siso._start_collector(siso_path, sockets_file, project)
|
|
mock_os_path_exists.assert_called_with(sockets_file)
|
|
mock_os_remove.assert_called_with(sockets_file)
|
|
|
|
|
|
def test_start_collector_remove_socket_file_fails(
|
|
start_collector_mocks: Dict[str, Any], mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
mock_os_path_exists = mocker.patch("os.path.exists", return_value=True)
|
|
mock_os_remove = mocker.patch("os.remove",
|
|
side_effect=OSError("Permission denied"))
|
|
mock_stderr = mocker.patch("sys.stderr", new_callable=io.StringIO)
|
|
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
sockets_file = os.path.join("/tmp", "test.sock")
|
|
_configure_http_responses(mocker,
|
|
start_collector_mocks["mock_conn"],
|
|
status_responses=[(404, None), (200, None)],
|
|
config_responses=[(200, None), (200, None)])
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config = {
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": siso._OTLP_DEFAULT_TCP_ENDPOINT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mocker.patch("siso.json.loads",
|
|
side_effect=[status_healthy, config, config])
|
|
siso._start_collector(siso_path, sockets_file, project)
|
|
|
|
mock_os_path_exists.assert_called_with(sockets_file)
|
|
mock_os_remove.assert_called_with(sockets_file)
|
|
assert f"Failed to remove {sockets_file}" in mock_stderr.getvalue()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"global_flags, subcmd_flags, args, subcmd, should_collect_logs, env, want, want_stderr",
|
|
[
|
|
pytest.param([], {}, ["other", "-C", "out/Default"], "other", True, {},
|
|
["other", "-C", "out/Default"], "",
|
|
id="no_ninja"),
|
|
pytest.param(
|
|
[], {}, ["ninja", "-C", "out/Default"], "ninja", False, {}, [
|
|
"ninja",
|
|
"-C",
|
|
"out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
],
|
|
"",
|
|
id="ninja_no_logs"),
|
|
pytest.param(
|
|
[], {}, ["ninja", "-C", "out/Default"], "ninja", True, {}, [
|
|
"ninja",
|
|
"-C",
|
|
"out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
],
|
|
"",
|
|
id="ninja_with_logs_no_project"),
|
|
pytest.param(
|
|
[], {},
|
|
["ninja", "-C", "out/Default", "--project=test-project"], "ninja",
|
|
True, {}, [
|
|
"ninja",
|
|
"-C",
|
|
"out/Default",
|
|
"--project=test-project",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
"--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler",
|
|
"--enable_cloud_trace",
|
|
"--enable_cloud_logging",
|
|
"--enable_collector",
|
|
"--metrics_project=test-project",
|
|
],
|
|
"",
|
|
id="ninja_with_logs_with_project_in_args"),
|
|
pytest.param(
|
|
[], {}, ["ninja", "-C", "out/Default"], "ninja", True,
|
|
{"SISO_PROJECT": "test-project"},
|
|
[
|
|
"ninja",
|
|
"-C",
|
|
"out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
"--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler",
|
|
"--enable_cloud_trace",
|
|
"--enable_cloud_logging",
|
|
"--enable_collector",
|
|
"--metrics_project=test-project",
|
|
],
|
|
"",
|
|
id="ninja_with_logs_with_project_in_env"),
|
|
pytest.param(
|
|
["-gflag"], {"ninja": ["-sflag"]}, ["ninja", "-C", "out/Default"],
|
|
"ninja", False, {}, [
|
|
"-gflag", "ninja", "-sflag", "-C", "out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
],
|
|
"depot_tools/siso.py: %s\n" \
|
|
% shlex.join(["-gflag", "ninja", "-sflag", "-C", "out/Default"]),
|
|
id="with_sisorc"),
|
|
pytest.param(
|
|
["-gflag_only"],
|
|
{},
|
|
["ninja", "-C", "out/Default"],
|
|
"ninja",
|
|
False,
|
|
{},
|
|
[
|
|
"-gflag_only",
|
|
"ninja",
|
|
"-C",
|
|
"out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
],
|
|
"depot_tools/siso.py: %s\n"
|
|
% shlex.join(["-gflag_only", "ninja", "-C", "out/Default"]),
|
|
id="with_sisorc_global_flags_only"
|
|
),
|
|
pytest.param(
|
|
[],
|
|
{"ninja": ["-sflag_only"]},
|
|
["ninja", "-C", "out/Default"],
|
|
"ninja",
|
|
False,
|
|
{},
|
|
[
|
|
"ninja",
|
|
"-sflag_only",
|
|
"-C",
|
|
"out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
],
|
|
"depot_tools/siso.py: %s\n"
|
|
% shlex.join(["ninja", "-sflag_only", "-C", "out/Default"]),
|
|
id="with_sisorc_subcmd_flags_only"
|
|
),
|
|
pytest.param(
|
|
["-gflag_tel"],
|
|
{"ninja": ["-sflag_tel"]},
|
|
["ninja", "-C", "out/Default"],
|
|
"ninja",
|
|
True,
|
|
{"SISO_PROJECT": "telemetry-project"},
|
|
[
|
|
"-gflag_tel",
|
|
"ninja",
|
|
"-sflag_tel",
|
|
"-C",
|
|
"out/Default",
|
|
"--metrics_labels",
|
|
f"type=developer,tool=siso,host_os={siso._SYSTEM_DICT.get(sys.platform, sys.platform)}",
|
|
"--enable_cloud_monitoring",
|
|
"--enable_cloud_profiler",
|
|
"--enable_cloud_trace",
|
|
"--enable_cloud_logging",
|
|
"--enable_collector",
|
|
"--metrics_project=telemetry-project",
|
|
],
|
|
"depot_tools/siso.py: %s\n"
|
|
% shlex.join(["-gflag_tel", "ninja", "-sflag_tel", "-C", "out/Default"]),
|
|
id="with_sisorc_global_and_subcmd_flags_and_telemetry"
|
|
),
|
|
pytest.param(
|
|
["-gflag_non_ninja"],
|
|
{"other_subcmd": ["-sflag_non_ninja"]},
|
|
["other_subcmd", "-C", "out/Default"],
|
|
"other_subcmd",
|
|
True,
|
|
{"SISO_PROJECT": "telemetry-project"},
|
|
[
|
|
"-gflag_non_ninja",
|
|
"other_subcmd",
|
|
"-sflag_non_ninja",
|
|
"-C",
|
|
"out/Default",
|
|
],
|
|
"depot_tools/siso.py: %s\n"
|
|
% shlex.join(["-gflag_non_ninja", "other_subcmd", "-sflag_non_ninja", "-C", "out/Default"]),
|
|
id="with_sisorc_non_ninja_subcmd"
|
|
),])
|
|
def test_process_args(global_flags: List[str], subcmd_flags: Dict[str,
|
|
List[str]],
|
|
args: List[str], subcmd: str, should_collect_logs: bool,
|
|
env: Dict[str, str], want: List[str], want_stderr: str,
|
|
siso_test_fixture: Any, mocker: Any) -> None:
|
|
mock_stderr = mocker.patch("sys.stderr", new_callable=io.StringIO)
|
|
got = siso._process_args(global_flags, subcmd_flags, args, subcmd,
|
|
should_collect_logs, env)
|
|
assert got == want
|
|
assert mock_stderr.getvalue() == want_stderr
|
|
|
|
|
|
# Else it won"t even compile on Windows.
|
|
if sys.platform != "win32":
|
|
SIGKILL = siso.signal.SIGKILL # pylint: disable=no-member
|
|
else:
|
|
SIGKILL = None
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Not applicable on Windows")
|
|
@pytest.mark.parametrize(
|
|
"stdout, stderr, returncode, kill_side_effect, expected_result, expected_kill_args",
|
|
[
|
|
pytest.param(
|
|
b"123\n", b"", 0, None, True,
|
|
(123, SIGKILL), id="found_and_killed"),
|
|
pytest.param(b"",
|
|
b"lsof: no process found\n",
|
|
1,
|
|
None,
|
|
False,
|
|
None,
|
|
id="process_not_found"),
|
|
pytest.param(b"123\n",
|
|
b"",
|
|
0,
|
|
OSError("Operation not permitted"),
|
|
False, (123, SIGKILL),
|
|
id="kill_fails"),
|
|
pytest.param(b"\n", b"", 0, None, False, None, id="no_pids_found"),
|
|
pytest.param(b"0\n123\n456\n",
|
|
b"",
|
|
0,
|
|
None,
|
|
True, (123, SIGKILL),
|
|
id="multiple_pids_found"),
|
|
])
|
|
def test_kill_collector_posix(stdout: bytes, stderr: bytes, returncode: int,
|
|
kill_side_effect: Optional[OSError],
|
|
expected_result: bool,
|
|
expected_kill_args: Optional[Tuple[int, Any]],
|
|
mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
mock_os_kill = mocker.patch("siso.os.kill")
|
|
mock_subprocess_run = mocker.patch("siso.subprocess.run")
|
|
mock_subprocess_run.return_value = mocker.Mock(stdout=stdout,
|
|
stderr=stderr,
|
|
returncode=returncode)
|
|
mock_os_kill.side_effect = kill_side_effect
|
|
|
|
result = siso._kill_collector()
|
|
|
|
assert result == expected_result
|
|
mock_subprocess_run.assert_called_once_with(
|
|
["lsof", "-t", f"-i:{siso._OTLP_HEALTH_PORT}"], capture_output=True)
|
|
if expected_kill_args:
|
|
mock_os_kill.assert_called_once_with(*expected_kill_args)
|
|
else:
|
|
mock_os_kill.assert_not_called()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform != "win32", reason="Only for Windows")
|
|
@pytest.mark.parametrize("run_effects, expected_result, expected_calls", [
|
|
pytest.param([
|
|
(f" TCP 127.0.0.1:{siso._OTLP_HEALTH_PORT} [::]:0 LISTENING 1234\r\n"
|
|
.encode("utf-8"), b"", 0),
|
|
(b"", b"", 0),
|
|
],
|
|
True, [
|
|
["netstat", "-aon"],
|
|
["taskkill", "/F", "/T", "/PID", "1234"],
|
|
],
|
|
id="found_and_killed"),
|
|
pytest.param([
|
|
(b" TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 868\r\n",
|
|
b"", 0),
|
|
],
|
|
False, [["netstat", "-aon"]],
|
|
id="process_not_found"),
|
|
pytest.param([
|
|
(f" TCP 127.0.0.1:{siso._OTLP_HEALTH_PORT} [::]:0 LISTENING 1234\r\n TCP 127.0.0.1:{siso._OTLP_HEALTH_PORT} [::]:0 LISTENING 5678\r\n"
|
|
.encode("utf-8"), b"", 0),
|
|
(b"", b"", 0),
|
|
],
|
|
True, [
|
|
["netstat", "-aon"],
|
|
["taskkill", "/F", "/T", "/PID", "1234"],
|
|
],
|
|
id="multiple_pids_found"),
|
|
pytest.param([
|
|
(b"", b"netstat error\n", 1),
|
|
],
|
|
False, [["netstat", "-aon"]],
|
|
id="netstat_fails"),
|
|
pytest.param([
|
|
(f" TCP 127.0.0.1:{siso._OTLP_HEALTH_PORT} [::]:0 LISTENING 1234\r\n"
|
|
.encode("utf-8"), b"", 0),
|
|
(b"", b"ERROR: Cannot terminate process.", 1),
|
|
],
|
|
False, [
|
|
["netstat", "-aon"],
|
|
["taskkill", "/F", "/T", "/PID", "1234"],
|
|
],
|
|
id="taskkill_fails"),
|
|
])
|
|
def test_kill_collector_windows(run_effects: List[Tuple[bytes, bytes, int]],
|
|
expected_result: bool,
|
|
expected_calls: List[List[str]],
|
|
mocker: Any) -> None:
|
|
mock_subprocess_run = mocker.patch("siso.subprocess.run")
|
|
mock_subprocess_run.side_effect = [
|
|
mocker.Mock(stdout=stdout, stderr=stderr, returncode=returncode)
|
|
for stdout, stderr, returncode in run_effects
|
|
]
|
|
|
|
result = siso._kill_collector()
|
|
|
|
assert result == expected_result
|
|
calls = [mocker.call(c, capture_output=True) for c in expected_calls]
|
|
mock_subprocess_run.assert_has_calls(calls)
|
|
assert mock_subprocess_run.call_count == len(calls)
|
|
|
|
|
|
def test_start_collector_subcommand_not_present(
|
|
start_collector_mocks: Dict[str, Any]) -> None:
|
|
m = start_collector_mocks
|
|
m["is_subcommand_present"].return_value = False
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
result = siso._start_collector(siso_path, None, project)
|
|
assert not result
|
|
m["is_subcommand_present"].assert_called_once_with(siso_path, "collector")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"platform, creationflags",
|
|
[
|
|
("linux", 0),
|
|
("win32", 512), # subprocess.CREATE_NEW_PROCESS_GROUP
|
|
])
|
|
def test_start_collector_dead_then_healthy(platform: str, creationflags: int,
|
|
start_collector_mocks: Dict[str,
|
|
Any],
|
|
mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new=platform)
|
|
mocker.patch("subprocess.CREATE_NEW_PROCESS_GROUP",
|
|
creationflags,
|
|
create=True)
|
|
mock_json_loads = mocker.patch("siso.json.loads")
|
|
m = start_collector_mocks
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
|
|
_configure_http_responses(mocker,
|
|
m["mock_conn"],
|
|
status_responses=[(404, None), (200, None)],
|
|
config_responses=[(200, None)])
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config = {
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": siso._OTLP_DEFAULT_TCP_ENDPOINT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mock_json_loads.side_effect = [status_healthy, config]
|
|
|
|
result = siso._start_collector(siso_path, None, project)
|
|
|
|
assert result
|
|
m["subprocess_popen"].assert_called_once_with(
|
|
[siso_path, "collector", "--project", project],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
creationflags=creationflags)
|
|
m["kill_collector"].assert_not_called()
|
|
|
|
|
|
def test_start_collector_unhealthy_then_healthy(
|
|
start_collector_mocks: Dict[str, Any], mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
mock_json_loads = mocker.patch("siso.json.loads")
|
|
m = start_collector_mocks
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
_configure_http_responses(mocker,
|
|
m["mock_conn"],
|
|
status_responses=[(200, None), (200, None)],
|
|
config_responses=[(200, None), (200, None)])
|
|
|
|
status_unhealthy = {"healthy": False, "status": "NotOK"}
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config_project_full = {
|
|
"exporters": {
|
|
"googlecloud": {
|
|
"project": project
|
|
}
|
|
},
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": siso._OTLP_DEFAULT_TCP_ENDPOINT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mock_json_loads.side_effect = [
|
|
status_unhealthy, status_healthy, config_project_full,
|
|
config_project_full
|
|
]
|
|
|
|
result = siso._start_collector(siso_path, None, project)
|
|
|
|
assert result
|
|
m["subprocess_popen"].assert_called_once_with(
|
|
[siso_path, "collector", "--project", project],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
creationflags=0)
|
|
m["kill_collector"].assert_called_once()
|
|
|
|
|
|
def test_start_collector_already_healthy(start_collector_mocks: Dict[str, Any],
|
|
mocker: Any) -> None:
|
|
mock_json_loads = mocker.patch("siso.json.loads")
|
|
m = start_collector_mocks
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
_configure_http_responses(mocker,
|
|
m["mock_conn"],
|
|
status_responses=[(200, None)],
|
|
config_responses=[(200, None), (200, None)])
|
|
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config_project_full = {
|
|
"exporters": {
|
|
"googlecloud": {
|
|
"project": project
|
|
}
|
|
},
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": siso._OTLP_DEFAULT_TCP_ENDPOINT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mock_json_loads.side_effect = [
|
|
status_healthy, config_project_full, config_project_full
|
|
]
|
|
|
|
result = siso._start_collector(siso_path, None, project)
|
|
|
|
assert result
|
|
m["subprocess_popen"].assert_not_called()
|
|
m["kill_collector"].assert_not_called()
|
|
|
|
|
|
def test_start_collector_never_healthy(start_collector_mocks: Dict[str, Any],
|
|
mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
m = start_collector_mocks
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
_configure_http_responses(mocker,
|
|
m["mock_conn"],
|
|
status_responses=[(404, None)])
|
|
|
|
siso._start_collector(siso_path, None, project)
|
|
|
|
m["subprocess_popen"].assert_called_once_with(
|
|
[siso_path, "collector", "--project", project],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
creationflags=0)
|
|
m["kill_collector"].assert_not_called()
|
|
|
|
|
|
def test_start_collector_healthy_after_retries(start_collector_mocks: Dict[str,
|
|
Any],
|
|
mocker: Any) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
mock_json_loads = mocker.patch("siso.json.loads")
|
|
m = start_collector_mocks
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
_configure_http_responses(mocker,
|
|
m["mock_conn"],
|
|
status_responses=[(404, None), (404, None),
|
|
(404, None), (200, None)],
|
|
config_responses=[(200, None), (200, None)])
|
|
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config_project_full = {
|
|
"exporters": {
|
|
"googlecloud": {
|
|
"project": project
|
|
}
|
|
},
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": siso._OTLP_DEFAULT_TCP_ENDPOINT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mock_json_loads.side_effect = [
|
|
status_healthy, config_project_full, config_project_full
|
|
]
|
|
|
|
result = siso._start_collector(siso_path, None, project)
|
|
|
|
assert result
|
|
m["subprocess_popen"].assert_called_once_with(
|
|
[siso_path, "collector", "--project", project],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
creationflags=0)
|
|
m["kill_collector"].assert_not_called()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"os_path_exists_side_effect, expected_result, http_status_responses, json_loads_side_effect_values, expected_os_exists_calls",
|
|
[
|
|
(itertools.repeat(True), True, [
|
|
(404, None), (200, None)
|
|
], ["status_healthy", "config_with_socket"], 2),
|
|
([False] * 8 + [True], True, [(404, None)] + [(200, None)] * 9,
|
|
["status_healthy", "config_with_socket"] * 9, 9),
|
|
([False] * 10, False, [(404, None)] + [(200, None)] * 9,
|
|
["status_healthy", "config_with_socket"] * 9, 10),
|
|
],
|
|
ids=["socket_exists", "appears_later", "never_appears"])
|
|
def test_start_collector_with_sockets_file(
|
|
start_collector_mocks: Dict[str, Any], mocker: Any,
|
|
os_path_exists_side_effect: Any, expected_result: bool,
|
|
http_status_responses: List[Tuple[int, Any]],
|
|
json_loads_side_effect_values: List[str],
|
|
expected_os_exists_calls: int) -> None:
|
|
mocker.patch("sys.platform", new="linux")
|
|
mock_json_loads = mocker.patch("siso.json.loads")
|
|
mocker.patch("os.path.isfile", return_value=False)
|
|
mock_os_exists = mocker.patch("os.path.exists")
|
|
mocker.patch("os.remove")
|
|
siso_path = "siso_path"
|
|
project = "test-project"
|
|
sockets_file = os.path.join("/tmp", "test-socket.sock")
|
|
|
|
status_healthy = {"healthy": True, "status": "StatusOK"}
|
|
config_with_socket = {
|
|
"receivers": {
|
|
"otlp": {
|
|
"protocols": {
|
|
"grpc": {
|
|
"endpoint": sockets_file
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
json_loads_map = {
|
|
"status_healthy": status_healthy,
|
|
"config_with_socket": config_with_socket,
|
|
}
|
|
json_loads_side_effect = [
|
|
json_loads_map[v] for v in json_loads_side_effect_values
|
|
]
|
|
|
|
m = start_collector_mocks
|
|
mock_os_exists.side_effect = os_path_exists_side_effect
|
|
mock_json_loads.side_effect = json_loads_side_effect
|
|
|
|
_configure_http_responses(mocker,
|
|
m["mock_conn"],
|
|
status_responses=list(http_status_responses),
|
|
config_responses=[(200, None)] * 20)
|
|
|
|
result = siso._start_collector(siso_path, sockets_file, project)
|
|
|
|
assert result == expected_result
|
|
m["subprocess_popen"].assert_called_once_with([
|
|
siso_path, "collector", "--project", project, "--collector_address",
|
|
f"unix://{sockets_file}"
|
|
],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
creationflags=0)
|
|
m["kill_collector"].assert_not_called()
|
|
assert mock_os_exists.call_count == expected_os_exists_calls
|
|
|
|
|
|
# Stanza to have pytest be executed.
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main([__file__] + sys.argv[1:]))
|