chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,804 @@
import os
import sys
import tempfile
from pathlib import Path
import jsonschema
import pytest
import yaml
from ray import job_config
from ray._private.runtime_env import validation
from ray._private.runtime_env.pip import _get_pip_hash
from ray._private.runtime_env.plugin_schema_manager import RuntimeEnvPluginSchemaManager
from ray._private.runtime_env.validation import (
parse_and_validate_conda,
parse_and_validate_excludes,
parse_and_validate_py_modules,
parse_and_validate_working_dir,
)
from ray.runtime_env import RuntimeEnv
from ray.runtime_env.runtime_env import (
_validate_no_local_paths,
)
_CONDA_DICT = {"dependencies": ["pip", {"pip": ["pip-install-test==0.5"]}]}
_PIP_LIST = ["requests==1.0.0", "pip-install-test"]
@pytest.fixture
def test_directory():
with tempfile.TemporaryDirectory() as tmp_dir:
path = Path(tmp_dir)
subdir = path / "subdir"
subdir.mkdir(parents=True)
requirements_file = subdir / "requirements.txt"
with requirements_file.open(mode="w") as f:
print("\n".join(_PIP_LIST), file=f)
good_conda_file = subdir / "good_conda_env.yaml"
with good_conda_file.open(mode="w") as f:
yaml.dump(_CONDA_DICT, f)
bad_conda_file = subdir / "bad_conda_env.yaml"
with bad_conda_file.open(mode="w") as f:
print("% this is not a YAML file %", file=f)
old_dir = os.getcwd()
os.chdir(tmp_dir)
yield subdir, requirements_file, good_conda_file, bad_conda_file
os.chdir(old_dir)
@pytest.fixture
def set_runtime_env_plugin_schemas(request):
runtime_env_plugin_schemas = getattr(request, "param", "0")
try:
os.environ["RAY_RUNTIME_ENV_PLUGIN_SCHEMAS"] = runtime_env_plugin_schemas
# Clear and reload schemas.
RuntimeEnvPluginSchemaManager.clear()
yield runtime_env_plugin_schemas
finally:
del os.environ["RAY_RUNTIME_ENV_PLUGIN_SCHEMAS"]
def test_key_with_value_none():
parsed_runtime_env = RuntimeEnv(pip=None)
assert parsed_runtime_env == {}
class TestValidateWorkingDir:
def test_validate_bad_path(self):
with pytest.raises(ValueError, match="a valid path"):
parse_and_validate_working_dir("/does/not/exist")
def test_validate_bad_uri(self):
with pytest.raises(ValueError, match="a valid URI"):
parse_and_validate_working_dir("unknown://abc")
def test_validate_invalid_type(self):
with pytest.raises(TypeError):
parse_and_validate_working_dir(1)
def test_validate_remote_invalid_extensions(self):
for uri in [
"https://some_domain.com/path/file",
"s3://bucket/file",
"gs://bucket/file",
]:
with pytest.raises(
ValueError,
match=r"Only \.zip, \.whl, \.tar\.gz, and \.tgz files supported for remote URIs\.",
):
parse_and_validate_working_dir(uri)
def test_validate_remote_valid_input(self):
for uri in [
"https://some_domain.com/path/file.zip",
"s3://bucket/file.zip",
"gs://bucket/file.zip",
"https://some_domain.com/path/file.tar.gz",
"s3://bucket/file.tar.gz",
"gs://bucket/file.tgz",
]:
working_dir = parse_and_validate_working_dir(uri)
assert working_dir == uri
def test_validate_path_valid_input(self, test_directory):
test_dir, _, _, _ = test_directory
valid_working_dir_path = str(test_dir)
working_dir = parse_and_validate_working_dir(str(valid_working_dir_path))
assert working_dir == valid_working_dir_path
class TestValidatePyModules:
def test_validate_not_a_list(self):
with pytest.raises(TypeError, match="must be a list of strings"):
parse_and_validate_py_modules(".")
def test_validate_bad_path(self):
with pytest.raises(ValueError, match="a valid path"):
parse_and_validate_py_modules(["/does/not/exist"])
def test_validate_bad_uri(self):
with pytest.raises(ValueError, match="a valid URI"):
parse_and_validate_py_modules(["unknown://abc"])
def test_validate_invalid_type(self):
with pytest.raises(TypeError):
parse_and_validate_py_modules([1])
def test_validate_remote_invalid_extension(self):
uris = [
"https://some_domain.com/path/file",
"s3://bucket/file",
"gs://bucket/file",
]
with pytest.raises(
ValueError,
match="Only .zip, .whl, .tar.gz, and .tgz files supported for remote URIs.",
):
parse_and_validate_py_modules(uris)
def test_validate_remote_valid_input(self):
uris = [
"https://some_domain.com/path/file.zip",
"s3://bucket/file.zip",
"gs://bucket/file.zip",
"https://some_domain.com/path/file.whl",
"s3://bucket/file.whl",
"gs://bucket/file.whl",
"https://some_domain.com/path/file.tar.gz",
"s3://bucket/file.tar.gz",
"gs://bucket/file.tgz",
]
py_modules = parse_and_validate_py_modules(uris)
assert py_modules == uris
def test_validate_path_valid_input(self, test_directory):
test_dir, _, _, _ = test_directory
paths = [str(test_dir)]
py_modules = parse_and_validate_py_modules(paths)
assert py_modules == paths
def test_validate_path_and_uri_valid_input(self, test_directory):
test_dir, _, _, _ = test_directory
uris_and_paths = [
str(test_dir),
"https://some_domain.com/path/file.zip",
"s3://bucket/file.zip",
"gs://bucket/file.zip",
"https://some_domain.com/path/file.whl",
"s3://bucket/file.whl",
"gs://bucket/file.whl",
]
py_modules = parse_and_validate_py_modules(uris_and_paths)
assert py_modules == uris_and_paths
class TestValidateExcludes:
def test_validate_excludes_invalid_types(self):
with pytest.raises(TypeError):
parse_and_validate_excludes(1)
with pytest.raises(TypeError):
parse_and_validate_excludes(True)
with pytest.raises(TypeError):
parse_and_validate_excludes("string")
with pytest.raises(TypeError):
parse_and_validate_excludes(["string", 1])
def test_validate_excludes_empty_list(self):
assert RuntimeEnv(excludes=[]) == {}
class TestValidateConda:
def test_validate_conda_invalid_types(self):
with pytest.raises(TypeError):
parse_and_validate_conda(1)
with pytest.raises(TypeError):
parse_and_validate_conda(True)
def test_validate_conda_str(self):
assert parse_and_validate_conda("my_env_name") == "my_env_name"
def test_validate_conda_invalid_path(self):
with pytest.raises(ValueError):
parse_and_validate_conda("../bad_path.yaml")
@pytest.mark.parametrize("absolute_path", [True, False])
def test_validate_conda_valid_file(self, test_directory, absolute_path):
_, _, good_conda_file, _ = test_directory
if absolute_path:
good_conda_file = good_conda_file.resolve()
assert parse_and_validate_conda(str(good_conda_file)) == _CONDA_DICT
@pytest.mark.parametrize("absolute_path", [True, False])
def test_validate_conda_invalid_file(self, test_directory, absolute_path):
_, _, _, bad_conda_file = test_directory
if absolute_path:
bad_conda_file = bad_conda_file.resolve()
with pytest.raises(ValueError):
parse_and_validate_conda(str(bad_conda_file))
def test_validate_conda_valid_dict(self):
assert parse_and_validate_conda(_CONDA_DICT) == _CONDA_DICT
class TestParsedRuntimeEnv:
def test_empty(self):
assert RuntimeEnv() == {}
def test_serialization(self):
env1 = RuntimeEnv(pip=["requests"], env_vars={"hi1": "hi1", "hi2": "hi2"})
env2 = RuntimeEnv(env_vars={"hi2": "hi2", "hi1": "hi1"}, pip=["requests"])
assert env1 == env2
serialized_env1 = env1.serialize()
serialized_env2 = env2.serialize()
# Key ordering shouldn't matter.
assert serialized_env1 == serialized_env2
deserialized_env1 = RuntimeEnv.deserialize(serialized_env1)
deserialized_env2 = RuntimeEnv.deserialize(serialized_env2)
assert env1 == deserialized_env1 == env2 == deserialized_env2
def test_reject_pip_and_conda(self):
with pytest.raises(ValueError):
RuntimeEnv(pip=["requests"], conda="env_name")
def test_ray_commit_injection(self):
# Should not be injected if no pip and conda.
result = RuntimeEnv(env_vars={"hi": "hi"})
assert "_ray_commit" not in result
# Should be injected if pip or conda present.
result = RuntimeEnv(pip=["requests"])
assert "_ray_commit" in result
result = RuntimeEnv(conda="env_name")
assert "_ray_commit" in result
# Should not override if passed.
result = RuntimeEnv(conda="env_name", _ray_commit="Blah")
assert result["_ray_commit"] == "Blah"
def test_inject_current_ray(self):
# Should not be injected if not provided by env var.
result = RuntimeEnv(env_vars={"hi": "hi"})
assert "_inject_current_ray" not in result
os.environ["RAY_RUNTIME_ENV_LOCAL_DEV_MODE"] = "1"
# Should be injected if provided by env var.
result = RuntimeEnv()
assert result["_inject_current_ray"]
# Should be preserved if passed.
result = RuntimeEnv(_inject_current_ray=False)
assert not result["_inject_current_ray"]
del os.environ["RAY_RUNTIME_ENV_LOCAL_DEV_MODE"]
class TestParseJobConfig:
def test_parse_runtime_env_from_json_env_variable(self):
job_config_json = {"runtime_env": {"working_dir": "uri://abc"}}
config = job_config.JobConfig.from_json(job_config_json)
assert config.runtime_env == job_config_json.get("runtime_env")
assert config.metadata == {}
schemas_dir = os.path.dirname(__file__)
test_env_1 = os.path.join(
os.path.dirname(__file__), "test_runtime_env_validation_1_schema.json"
)
test_env_2 = os.path.join(
os.path.dirname(__file__), "test_runtime_env_validation_2_schema.json"
)
test_env_invalid_path = os.path.join(
os.path.dirname(__file__), "test_runtime_env_validation_non_existent.json"
)
test_env_bad_json = os.path.join(
os.path.dirname(__file__), "test_runtime_env_validation_bad_schema.json"
)
@pytest.mark.parametrize(
"set_runtime_env_plugin_schemas",
[
schemas_dir,
f"{test_env_1},{test_env_2}",
# Test with an invalid JSON file first in the list
f"{test_env_bad_json},{test_env_1},{test_env_2}",
# Test with a non-existent JSON file
f"{test_env_invalid_path},{test_env_1},{test_env_2}",
],
indirect=True,
)
@pytest.mark.skipif(sys.platform == "win32", reason="Failing on Windows.")
class TestValidateByJsonSchema:
def test_validate_pip(self, set_runtime_env_plugin_schemas):
runtime_env = RuntimeEnv()
runtime_env.set("pip", {"packages": ["requests"], "pip_check": True})
with pytest.raises(jsonschema.exceptions.ValidationError, match="pip_check"):
runtime_env.set("pip", {"packages": ["requests"], "pip_check": "1"})
runtime_env["pip"] = {"packages": ["requests"], "pip_check": True}
with pytest.raises(jsonschema.exceptions.ValidationError, match="pip_check"):
runtime_env["pip"] = {"packages": ["requests"], "pip_check": "1"}
def test_validate_working_dir(self, set_runtime_env_plugin_schemas):
runtime_env = RuntimeEnv()
runtime_env.set("working_dir", "https://abc/file.zip")
with pytest.raises(jsonschema.exceptions.ValidationError, match="working_dir"):
runtime_env.set("working_dir", ["https://abc/file.zip"])
runtime_env["working_dir"] = "https://abc/file.zip"
with pytest.raises(jsonschema.exceptions.ValidationError, match="working_dir"):
runtime_env["working_dir"] = ["https://abc/file.zip"]
def test_validate_test_env_1(self, set_runtime_env_plugin_schemas):
runtime_env = RuntimeEnv()
runtime_env.set("test_env_1", {"array": ["123"], "bool": True})
with pytest.raises(jsonschema.exceptions.ValidationError, match="bool"):
runtime_env.set("test_env_1", {"array": ["123"], "bool": "1"})
def test_validate_test_env_2(self, set_runtime_env_plugin_schemas):
runtime_env = RuntimeEnv()
runtime_env.set("test_env_2", "123")
with pytest.raises(jsonschema.exceptions.ValidationError, match="test_env_2"):
runtime_env.set("test_env_2", ["123"])
@pytest.mark.skipif(sys.platform == "win32", reason="Failing on Windows.")
class TestRuntimeEnvPluginSchemaManager:
def test(self):
RuntimeEnvPluginSchemaManager.clear()
# No schemas when starts.
assert len(RuntimeEnvPluginSchemaManager.schemas) == 0
# When the `validate` is used first time, the schemas will be loaded lazily.
# The validation of pip is enabled.
with pytest.raises(jsonschema.exceptions.ValidationError, match="pip_check"):
RuntimeEnvPluginSchemaManager.validate(
"pip", {"packages": ["requests"], "pip_check": "123"}
)
# The validation of test_env_1 is disabled because we haven't set the env var.
RuntimeEnvPluginSchemaManager.validate(
"test_env_1", {"array": ["123"], "bool": "123"}
)
assert len(RuntimeEnvPluginSchemaManager.schemas) != 0
# Set the thirdparty schemas.
os.environ["RAY_RUNTIME_ENV_PLUGIN_SCHEMAS"] = schemas_dir
# clear the loaded schemas to make sure the schemas chould be reloaded next
# time.
RuntimeEnvPluginSchemaManager.clear()
assert len(RuntimeEnvPluginSchemaManager.schemas) == 0
# The validation of test_env_1 is enabled.
with pytest.raises(jsonschema.exceptions.ValidationError, match="bool"):
RuntimeEnvPluginSchemaManager.validate(
"test_env_1", {"array": ["123"], "bool": "123"}
)
class TestValidateUV:
def test_parse_and_validate_uv(self, test_directory):
# Valid case w/o duplication.
result = validation.parse_and_validate_uv({"packages": ["tensorflow"]})
assert result == {
"packages": ["tensorflow"],
"uv_check": False,
"uv_pip_install_options": ["--no-cache"],
}
# Valid case w/ duplication.
result = validation.parse_and_validate_uv(
{"packages": ["tensorflow", "tensorflow"]}
)
assert result == {
"packages": ["tensorflow"],
"uv_check": False,
"uv_pip_install_options": ["--no-cache"],
}
# Valid case, use `list` to represent necessary packages.
result = validation.parse_and_validate_uv(
["requests==1.0.0", "aiohttp", "ray[serve]"]
)
assert result == {
"packages": ["requests==1.0.0", "aiohttp", "ray[serve]"],
"uv_check": False,
}
# Invalid case, unsupport keys.
with pytest.raises(ValueError):
result = validation.parse_and_validate_uv({"random_key": "random_value"})
# Valid case w/ uv version.
result = validation.parse_and_validate_uv(
{"packages": ["tensorflow"], "uv_version": "==0.4.30"}
)
assert result == {
"packages": ["tensorflow"],
"uv_version": "==0.4.30",
"uv_check": False,
"uv_pip_install_options": ["--no-cache"],
}
# Valid requirement files.
_, requirements_file, _, _ = test_directory
requirements_file = requirements_file.resolve()
result = validation.parse_and_validate_uv(str(requirements_file))
assert result == {
"packages": ["requests==1.0.0", "pip-install-test"],
"uv_check": False,
}
# Invalid requiremnt files.
with pytest.raises(ValueError):
result = validation.parse_and_validate_uv("some random non-existent file")
# Invalid uv install options.
with pytest.raises(TypeError):
result = validation.parse_and_validate_uv(
{
"packages": ["tensorflow"],
"uv_version": "==0.4.30",
"uv_pip_install_options": [1],
}
)
# Valid uv install options.
result = validation.parse_and_validate_uv(
{
"packages": ["tensorflow"],
"uv_version": "==0.4.30",
"uv_pip_install_options": ["--no-cache"],
}
)
assert result == {
"packages": ["tensorflow"],
"uv_check": False,
"uv_pip_install_options": ["--no-cache"],
"uv_version": "==0.4.30",
}
class TestValidatePip:
def test_validate_pip_invalid_types(self):
with pytest.raises(TypeError):
validation.parse_and_validate_pip(1)
with pytest.raises(TypeError):
validation.parse_and_validate_pip(True)
def test_validate_pip_invalid_path(self):
with pytest.raises(ValueError):
validation.parse_and_validate_pip("../bad_path.txt")
@pytest.mark.parametrize("absolute_path", [True, False])
def test_validate_pip_valid_file(self, test_directory, absolute_path):
_, requirements_file, _, _ = test_directory
if absolute_path:
requirements_file = requirements_file.resolve()
result = validation.parse_and_validate_pip(str(requirements_file))
assert result["packages"] == _PIP_LIST
assert not result["pip_check"]
assert "pip_version" not in result
def test_validate_pip_valid_list(self):
result = validation.parse_and_validate_pip(_PIP_LIST)
assert result["packages"] == _PIP_LIST
assert not result["pip_check"]
assert "pip_version" not in result
def test_validate_ray(self):
result = validation.parse_and_validate_pip(["pkg1", "ray", "pkg2"])
assert result["packages"] == ["pkg1", "ray", "pkg2"]
assert not result["pip_check"]
assert "pip_version" not in result
def test_validate_pip_install_options(self):
# Happy path for non-empty pip_install_options
opts = ["--no-cache-dir", "--no-build-isolation", "--disable-pip-version-check"]
result = validation.parse_and_validate_pip(
{
"packages": ["pkg1", "ray", "pkg2"],
"pip_install_options": list(opts),
}
)
assert result["packages"] == ["pkg1", "ray", "pkg2"]
assert not result["pip_check"]
assert "pip_version" not in result
assert result["pip_install_options"] == opts
# Happy path for missing pip_install_options. No default value for field
# to maintain backwards compatibility with ray==2.0.1
result = validation.parse_and_validate_pip(
{
"packages": ["pkg1", "ray", "pkg2"],
}
)
assert "pip_install_options" not in result
with pytest.raises(TypeError) as e:
validation.parse_and_validate_pip(
{
"packages": ["pkg1", "ray", "pkg2"],
"pip_install_options": [False],
}
)
assert "pip_install_options" in str(e) and "must be of type list[str]" in str(e)
with pytest.raises(TypeError) as e:
validation.parse_and_validate_pip(
{
"packages": ["pkg1", "ray", "pkg2"],
"pip_install_options": None,
}
)
assert "pip_install_options" in str(e) and "must be of type list[str]" in str(e)
class TestGetPipHash:
def test_pip_hash_with_requirements_file(self, test_directory):
_, requirements_file, _, _ = test_directory
req_path = str(requirements_file)
pip_dict1 = {"packages": [f"-r {req_path}"]}
hash1 = _get_pip_hash(pip_dict1)
pip_dict2 = {"packages": ["requests==1.0.0", "pip-install-test"]}
hash2 = _get_pip_hash(pip_dict2)
assert hash1 == hash2
def test_pip_hash_changes_with_file_content(self, test_directory):
_, requirements_file, _, _ = test_directory
req_path = str(requirements_file)
pip_dict = {"packages": [f"-r {req_path}"]}
with open(req_path, "w") as f:
f.write("numpy==1.21.0\n")
hash1 = _get_pip_hash(pip_dict)
with open(req_path, "w") as f:
f.write("numpy==1.22.0\n")
hash2 = _get_pip_hash(pip_dict)
assert hash1 != hash2
def test_pip_hash_with_comments_and_empty_lines(self, test_directory):
_, requirements_file, _, _ = test_directory
req_path = str(requirements_file)
with open(req_path, "w") as f:
f.write(
"# This is a comment\nnumpy==1.21.0\n\n# Another comment\npandas==1.3.0\n"
)
pip_dict = {"packages": [f"-r {req_path}"]}
hash1 = _get_pip_hash(pip_dict)
pip_dict2 = {"packages": ["numpy==1.21.0", "pandas==1.3.0"]}
hash2 = _get_pip_hash(pip_dict2)
assert hash1 == hash2
def test_pip_hash_without_r(self):
pip_dict = {"packages": ["numpy==1.21.0", "pandas==1.3.0"]}
hash1 = _get_pip_hash(pip_dict)
pip_dict2 = {"packages": ["numpy==1.21.0", "pandas==1.3.0"]}
hash2 = _get_pip_hash(pip_dict2)
assert hash1 == hash2
def test_pip_hash_different_packages(self):
pip_dict = {"packages": ["numpy==1.21.0"]}
hash1 = _get_pip_hash(pip_dict)
pip_dict2 = {"packages": ["pandas==1.3.0"]}
hash2 = _get_pip_hash(pip_dict2)
assert hash1 != hash2
def test_pip_hash_with_pip_install_options(self, test_directory):
_, requirements_file, _, _ = test_directory
req_path = str(requirements_file)
pip_dict = {
"packages": [f"-r {req_path}"],
"pip_install_options": ["--no-cache-dir"],
}
hash1 = _get_pip_hash(pip_dict)
pip_dict2 = {
"packages": [f"-r {req_path}"],
"pip_install_options": ["--disable-pip-version-check"],
}
hash2 = _get_pip_hash(pip_dict2)
assert hash1 != hash2
def test_pip_hash_with_circular_reference(self):
with tempfile.TemporaryDirectory() as tmpdir:
# Create file A
file_a = os.path.join(tmpdir, "a.txt")
with open(file_a, "w") as f:
f.write("-r b.txt\nnumpy==1.21.0\n")
# Create file B
file_b = os.path.join(tmpdir, "b.txt")
with open(file_b, "w") as f:
f.write("-r a.txt\npandas==1.3.0\n")
pip_dict = {"packages": [f"-r {file_a}"]}
hash_val = _get_pip_hash(pip_dict)
assert isinstance(hash_val, str)
assert len(hash_val) == 40
def test_pip_hash_with_self_reference(self):
with tempfile.TemporaryDirectory() as tmpdir:
# Create self-referencing file
self_file = os.path.join(tmpdir, "self.txt")
with open(self_file, "w") as f:
f.write("-r self.txt\nnumpy==1.21.0\n")
pip_dict = {"packages": [f"-r {self_file}"]}
hash_val = _get_pip_hash(pip_dict)
assert isinstance(hash_val, str)
assert len(hash_val) == 40
def test_pip_hash_with_nested_relative_paths(self):
with tempfile.TemporaryDirectory() as tmpdir:
# Create directory structure
reqs_dir = os.path.join(tmpdir, "reqs")
os.makedirs(reqs_dir)
# Create base.txt in reqs directory
base_file = os.path.join(reqs_dir, "base.txt")
with open(base_file, "w") as f:
f.write("-r extras.txt\nnumpy==1.21.0\n")
# Create extras.txt in the same directory (relative path)
extras_file = os.path.join(reqs_dir, "extras.txt")
with open(extras_file, "w") as f:
f.write("pandas==1.3.0\n")
# Test with absolute path to base.txt
pip_dict = {"packages": [f"-r {base_file}"]}
hash1 = _get_pip_hash(pip_dict)
# Test with relative path from tmpdir
original_cwd = os.getcwd()
try:
os.chdir(tmpdir)
relative_base = os.path.relpath(base_file, tmpdir)
pip_dict2 = {"packages": [f"-r {relative_base}"]}
hash2 = _get_pip_hash(pip_dict2)
# Hashes should be the same regardless of how we reference the file
assert hash1 == hash2
finally:
os.chdir(original_cwd)
assert isinstance(hash1, str)
assert len(hash1) == 40
def test_pip_hash_with_nested_circular_relative_paths(self):
with tempfile.TemporaryDirectory() as tmpdir:
# Create directory structure
reqs_dir = os.path.join(tmpdir, "reqs")
os.makedirs(reqs_dir)
# Create circular references with relative paths
a_file = os.path.join(reqs_dir, "a.txt")
with open(a_file, "w") as f:
f.write("-r b.txt\nnumpy==1.21.0\n")
b_file = os.path.join(reqs_dir, "b.txt")
with open(b_file, "w") as f:
f.write("-r a.txt\npandas==1.3.0\n")
# This should not cause an infinite loop
pip_dict = {"packages": [f"-r {a_file}"]}
hash_val = _get_pip_hash(pip_dict)
assert isinstance(hash_val, str)
assert len(hash_val) == 40
def test_pip_hash_with_long_form_requirement(self):
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write("numpy==1.21.0\n")
temp_file = f.name
try:
# Test with long-form --requirement flag
pip_dict = {"packages": [f"--requirement {temp_file}"]}
hash_val = _get_pip_hash(pip_dict)
assert isinstance(hash_val, str)
assert len(hash_val) == 40
finally:
os.unlink(temp_file)
def test_pip_hash_with_long_form_requirement_equals(self):
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write("numpy==1.21.0\n")
temp_file = f.name
try:
# Test with long-form --requirement=file.txt format
pip_dict = {"packages": [f"--requirement={temp_file}"]}
hash_val = _get_pip_hash(pip_dict)
assert isinstance(hash_val, str)
assert len(hash_val) == 40
finally:
os.unlink(temp_file)
def test_pip_hash_with_invalid_file(self):
with tempfile.TemporaryDirectory() as tmpdir:
# Test with non-existent file - should raise FileNotFoundError
non_existent_file = os.path.join(tmpdir, "non_existent.txt")
pip_dict = {"packages": [f"-r {non_existent_file}"]}
with pytest.raises(FileNotFoundError):
_get_pip_hash(pip_dict)
class TestValidateEnvVars:
def test_type_validation(self):
# Only strings allowed.
with pytest.raises(TypeError, match=".*Dict[str, str]*"):
validation.parse_and_validate_env_vars({"INT_ENV": 1})
with pytest.raises(TypeError, match=".*Dict[str, str]*"):
validation.parse_and_validate_env_vars({1: "hi"})
with pytest.raises(TypeError, match=".*value 123 is of type <class 'int'>*"):
validation.parse_and_validate_env_vars({"hi": 123})
with pytest.raises(TypeError, match=".*value True is of type <class 'bool'>*"):
validation.parse_and_validate_env_vars({"hi": True})
with pytest.raises(TypeError, match=".*key 1.23 is of type <class 'float'>*"):
validation.parse_and_validate_env_vars({1.23: "hi"})
def test_validate_no_local_paths_raises_exceptions_on_type_mismatch():
with pytest.raises(TypeError):
_validate_no_local_paths(1)
with pytest.raises(TypeError):
_validate_no_local_paths({})
def test_validate_no_local_paths_fails_if_local_working_dir():
with tempfile.TemporaryDirectory() as tmp_dir:
path = Path(tmp_dir)
working_dir = path / "working_dir"
working_dir.mkdir(parents=True)
working_dir_str = str(working_dir)
runtime_env = RuntimeEnv(working_dir=working_dir_str)
with pytest.raises(ValueError, match="not a valid URI"):
_validate_no_local_paths(runtime_env)
def test_validate_no_local_paths_fails_if_local_py_module():
with tempfile.NamedTemporaryFile(suffix=".whl") as tmp_file:
runtime_env = RuntimeEnv(py_modules=[tmp_file.name, "gcs://some_other_file"])
with pytest.raises(ValueError, match="not a valid URI"):
_validate_no_local_paths(runtime_env)
if __name__ == "__main__":
sys.exit(pytest.main(["-vv", __file__]))