mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-05 15:28:27 +01:00
Replaced Path* dataclasses with PathInfo/URLInfo/CloudURLInfo, which know how parse, serialize, stringify and join themselves. They also provide useful accessors like part of an url or pathlib.Path like .parent or .name. Along the way two families of bugs where fixed and should never occur in the future: - paths not matched as strings anymore, e.g. using str.startswith() - Output.path is eliminated as ambiguous Both families might had caused undefined surprising behavior when local and remote paths clash.
163 lines
4.6 KiB
Python
163 lines
4.6 KiB
Python
import getpass
|
|
import os
|
|
import sys
|
|
|
|
from unittest import TestCase
|
|
|
|
from mock import patch, mock_open
|
|
import pytest
|
|
|
|
from dvc.remote.ssh import RemoteSSH
|
|
|
|
|
|
class TestRemoteSSH(TestCase):
|
|
def test_url(self):
|
|
user = "test"
|
|
host = "123.45.67.89"
|
|
port = 1234
|
|
path = "/path/to/dir"
|
|
|
|
# URL ssh://[user@]host.xz[:port]/path
|
|
url = "ssh://{}@{}:{}{}".format(user, host, port, path)
|
|
config = {"url": url}
|
|
|
|
remote = RemoteSSH(None, config)
|
|
self.assertEqual(remote.path_info, url)
|
|
|
|
# SCP-like URL ssh://[user@]host.xz:/absolute/path
|
|
url = "ssh://{}@{}:{}".format(user, host, path)
|
|
config = {"url": url}
|
|
|
|
remote = RemoteSSH(None, config)
|
|
self.assertEqual(remote.path_info, url)
|
|
|
|
def test_no_path(self):
|
|
config = {"url": "ssh://127.0.0.1"}
|
|
remote = RemoteSSH(None, config)
|
|
self.assertEqual(remote.path_info.path, "")
|
|
|
|
|
|
mock_ssh_config = """
|
|
Host example.com
|
|
User ubuntu
|
|
HostName 1.2.3.4
|
|
Port 1234
|
|
IdentityFile ~/.ssh/not_default.key
|
|
"""
|
|
|
|
if sys.version_info.major == 3:
|
|
builtin_module_name = "builtins"
|
|
else:
|
|
builtin_module_name = "__builtin__"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"config,expected_host",
|
|
[
|
|
({"url": "ssh://example.com"}, "1.2.3.4"),
|
|
({"url": "ssh://not_in_ssh_config.com"}, "not_in_ssh_config.com"),
|
|
],
|
|
)
|
|
@patch("os.path.exists", return_value=True)
|
|
@patch(
|
|
"{}.open".format(builtin_module_name),
|
|
new_callable=mock_open,
|
|
read_data=mock_ssh_config,
|
|
)
|
|
def test_ssh_host_override_from_config(
|
|
mock_file, mock_exists, config, expected_host
|
|
):
|
|
remote = RemoteSSH(None, config)
|
|
|
|
mock_exists.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
mock_file.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
assert remote.path_info.host == expected_host
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"config,expected_user",
|
|
[
|
|
({"url": "ssh://[email protected]"}, "test1"),
|
|
({"url": "ssh://example.com", "user": "test2"}, "test2"),
|
|
({"url": "ssh://example.com"}, "ubuntu"),
|
|
({"url": "ssh://test1@not_in_ssh_config.com"}, "test1"),
|
|
(
|
|
{"url": "ssh://test1@not_in_ssh_config.com", "user": "test2"},
|
|
"test2",
|
|
),
|
|
({"url": "ssh://not_in_ssh_config.com"}, getpass.getuser()),
|
|
],
|
|
)
|
|
@patch("os.path.exists", return_value=True)
|
|
@patch(
|
|
"{}.open".format(builtin_module_name),
|
|
new_callable=mock_open,
|
|
read_data=mock_ssh_config,
|
|
)
|
|
def test_ssh_user(mock_file, mock_exists, config, expected_user):
|
|
remote = RemoteSSH(None, config)
|
|
|
|
mock_exists.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
mock_file.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
assert remote.path_info.user == expected_user
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"config,expected_port",
|
|
[
|
|
({"url": "ssh://example.com:2222"}, 2222),
|
|
({"url": "ssh://example.com"}, 1234),
|
|
({"url": "ssh://example.com", "port": 4321}, 4321),
|
|
({"url": "ssh://not_in_ssh_config.com"}, RemoteSSH.DEFAULT_PORT),
|
|
({"url": "ssh://not_in_ssh_config.com:2222"}, 2222),
|
|
({"url": "ssh://not_in_ssh_config.com:2222", "port": 4321}, 4321),
|
|
],
|
|
)
|
|
@patch("os.path.exists", return_value=True)
|
|
@patch(
|
|
"{}.open".format(builtin_module_name),
|
|
new_callable=mock_open,
|
|
read_data=mock_ssh_config,
|
|
)
|
|
def test_ssh_port(mock_file, mock_exists, config, expected_port):
|
|
remote = RemoteSSH(None, config)
|
|
|
|
mock_exists.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
mock_file.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
assert remote.path_info.port == expected_port
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"config,expected_keyfile",
|
|
[
|
|
(
|
|
{"url": "ssh://example.com", "keyfile": "dvc_config.key"},
|
|
"dvc_config.key",
|
|
),
|
|
(
|
|
{"url": "ssh://example.com"},
|
|
os.path.expanduser("~/.ssh/not_default.key"),
|
|
),
|
|
(
|
|
{
|
|
"url": "ssh://not_in_ssh_config.com",
|
|
"keyfile": "dvc_config.key",
|
|
},
|
|
"dvc_config.key",
|
|
),
|
|
({"url": "ssh://not_in_ssh_config.com"}, None),
|
|
],
|
|
)
|
|
@patch("os.path.exists", return_value=True)
|
|
@patch(
|
|
"{}.open".format(builtin_module_name),
|
|
new_callable=mock_open,
|
|
read_data=mock_ssh_config,
|
|
)
|
|
def test_ssh_keyfile(mock_file, mock_exists, config, expected_keyfile):
|
|
remote = RemoteSSH(None, config)
|
|
|
|
mock_exists.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
mock_file.assert_called_with(RemoteSSH.ssh_config_filename())
|
|
assert remote.keyfile == expected_keyfile
|