Skip to content

Commit ddb81d5

Browse files
committed
Teardown image at end of ssh tests, factor out ssh tests.
1 parent 75d3f43 commit ddb81d5

File tree

6 files changed

+460
-444
lines changed

6 files changed

+460
-444
lines changed

tests/ssh_test_utils.py

+45
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
import builtins
66
import copy
7+
import os
8+
import platform
79
import stat
810
import subprocess
911
import sys
1012
import warnings
13+
from pathlib import Path
1114

1215
import paramiko
1316

1417
from datashuttle.utils import rclone, ssh
1518

19+
PORT = 3306 # https://github.com/orgs/community/discussions/25550
20+
os.environ["DS_SSH_PORT"] = str(PORT)
21+
1622

1723
def setup_project_for_ssh(
1824
project, central_path, central_host_id, central_host_username
@@ -89,6 +95,45 @@ def setup_ssh_connection(project, setup_ssh_key_pair=True):
8995
return verified
9096

9197

98+
def setup_ssh_container(container_name):
99+
""""""
100+
assert docker_is_running(), (
101+
"docker is not running, "
102+
"this should be checked at the top of test script"
103+
)
104+
105+
image_path = Path(__file__).parent / "ssh_test_images"
106+
os.chdir(image_path)
107+
108+
if platform.system() == "Linux":
109+
build_command = "sudo docker build -t ssh_server ."
110+
run_command = f"sudo docker run -d -p {PORT}:22 --name {container_name} ssh_server"
111+
else:
112+
build_command = "docker build ."
113+
run_command = (
114+
f"docker run -d -p {PORT}:22 --name {container_name} ssh_server"
115+
)
116+
117+
build_output = subprocess.run(
118+
build_command,
119+
shell=True,
120+
capture_output=True,
121+
)
122+
assert (
123+
build_output.returncode == 0
124+
), f"docker build failed with: STDOUT-{build_output.stdout} STDERR-{build_output.stderr}"
125+
126+
run_output = subprocess.run(
127+
run_command,
128+
shell=True,
129+
capture_output=True,
130+
)
131+
132+
assert (
133+
run_output.returncode == 0
134+
), f"docker run failed with: STDOUT-{run_output.stdout} STDERR-{run_output.stderr}"
135+
136+
92137
def sftp_recursive_file_search(sftp, path_, all_filenames):
93138
try:
94139
sftp.stat(path_)

tests/tests_integration/base.py

+1-106
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import os
2-
import platform
3-
import subprocess
42
import warnings
5-
from pathlib import Path
63

74
import pytest
8-
import ssh_test_utils
95
import test_utils
10-
from file_conflicts_pathtable import get_pathtable
116

127
from datashuttle.datashuttle import DataShuttle
138

149
TEST_PROJECT_NAME = "test_project"
1510

1611

1712
class BaseTest:
13+
1814
@pytest.fixture(scope="function")
1915
def no_cfg_project(test):
2016
"""
@@ -61,104 +57,3 @@ def clean_project_name(self):
6157
test_utils.delete_project_if_it_exists(project_name)
6258
yield project_name
6359
test_utils.delete_project_if_it_exists(project_name)
64-
65-
@pytest.fixture(
66-
scope="class",
67-
)
68-
def pathtable_and_project(self, tmpdir_factory):
69-
"""
70-
Create a new test project with a test project folder
71-
and file structure (see `get_pathtable()` for definition).
72-
"""
73-
tmp_path = tmpdir_factory.mktemp("test")
74-
75-
base_path = tmp_path / "test with space"
76-
test_project_name = "test_file_conflicts"
77-
78-
project, cwd = test_utils.setup_project_fixture(
79-
base_path, test_project_name
80-
)
81-
82-
pathtable = get_pathtable(project.cfg["local_path"])
83-
84-
self.create_all_pathtable_files(pathtable)
85-
86-
yield [pathtable, project]
87-
88-
test_utils.teardown_project(cwd, project)
89-
90-
@pytest.fixture(
91-
scope="session",
92-
)
93-
def setup_ssh_container(self):
94-
""""""
95-
PORT = 3306 # https://github.com/orgs/community/discussions/25550
96-
os.environ["DS_SSH_PORT"] = str(PORT)
97-
98-
assert ssh_test_utils.docker_is_running(), (
99-
"docker is not running, "
100-
"this should be checked at the top of test script"
101-
)
102-
103-
image_path = Path(__file__).parent.parent / "ssh_test_images"
104-
os.chdir(image_path)
105-
106-
if platform.system() == "Linux":
107-
build_command = "sudo docker build -t ssh_server ."
108-
run_command = f"sudo docker run -d -p {PORT}:22 ssh_server"
109-
else:
110-
build_command = "docker build ."
111-
run_command = f"docker run -d -p {PORT}:22 ssh_server"
112-
113-
build_output = subprocess.run(
114-
build_command,
115-
shell=True,
116-
capture_output=True,
117-
)
118-
assert build_output.returncode == 0, (
119-
f"docker build failed with: STDOUT-{build_output.stdout} STDERR-"
120-
f"{build_output.stderr}"
121-
)
122-
123-
run_output = subprocess.run(
124-
run_command,
125-
shell=True,
126-
capture_output=True,
127-
)
128-
129-
assert run_output.returncode == 0, (
130-
f"docker run failed with: STDOUT-{run_output.stdout} STDE"
131-
f"RR-{run_output.stderr}"
132-
)
133-
134-
# setup_project_for_ssh(
135-
# project,
136-
# central_path=f"/home/sshuser/datashuttle/{project.project_name}",
137-
# central_host_id="localhost",
138-
# central_host_username="sshuser",
139-
# )
140-
141-
@pytest.fixture(
142-
scope="class",
143-
)
144-
def ssh_setup(self, pathtable_and_project, setup_ssh_container):
145-
"""
146-
After initial project setup (in `pathtable_and_project`)
147-
setup a container and the project's SSH connection to the container.
148-
Then upload the test project to the `central_path`.
149-
"""
150-
pathtable, project = pathtable_and_project
151-
152-
ssh_test_utils.setup_project_for_ssh(
153-
project,
154-
central_path=f"/home/sshuser/datashuttle/{project.project_name}",
155-
central_host_id="localhost",
156-
central_host_username="sshuser",
157-
)
158-
159-
# ssh_test_utils.setup_project_and_container_for_ssh(project)
160-
ssh_test_utils.setup_ssh_connection(project)
161-
162-
project.upload_rawdata()
163-
164-
return [pathtable, project]
+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
"""
2+
"""
3+
4+
import copy
5+
from pathlib import Path
6+
7+
import pandas as pd
8+
import pytest
9+
import test_utils
10+
from base import BaseTest
11+
from file_conflicts_pathtable import get_pathtable
12+
13+
14+
class BaseTransfer(BaseTest):
15+
16+
# ----------------------------------------------------------------------------------
17+
# Test File Transfer - All Options
18+
# ----------------------------------------------------------------------------------
19+
20+
@pytest.fixture(
21+
scope="class",
22+
)
23+
def pathtable_and_project(self, tmpdir_factory):
24+
"""
25+
Create a new test project with a test project folder
26+
and file structure (see `get_pathtable()` for definition).
27+
"""
28+
tmp_path = tmpdir_factory.mktemp("test")
29+
30+
base_path = tmp_path / "test with space"
31+
test_project_name = "test_file_conflicts"
32+
33+
project, cwd = test_utils.setup_project_fixture(
34+
base_path, test_project_name
35+
)
36+
37+
pathtable = get_pathtable(project.cfg["local_path"])
38+
39+
self.create_all_pathtable_files(pathtable)
40+
41+
yield [pathtable, project]
42+
43+
test_utils.teardown_project(cwd, project)
44+
45+
def get_expected_transferred_paths(
46+
self, pathtable, sub_names, ses_names, datatype
47+
):
48+
"""
49+
Process the expected files that are transferred using the logic in
50+
`make_pathtable_search_filter()` to
51+
"""
52+
parsed_sub_names = self.parse_arguments(pathtable, sub_names, "sub")
53+
parsed_ses_names = self.parse_arguments(pathtable, ses_names, "ses")
54+
parsed_datatype = self.parse_arguments(pathtable, datatype, "datatype")
55+
56+
# Filter pathtable to get files that were expected to be transferred
57+
(
58+
sub_ses_dtype_arguments,
59+
extra_arguments,
60+
) = self.make_pathtable_search_filter(
61+
parsed_sub_names, parsed_ses_names, parsed_datatype
62+
)
63+
64+
datatype_folders = self.query_table(pathtable, sub_ses_dtype_arguments)
65+
extra_folders = self.query_table(pathtable, extra_arguments)
66+
67+
expected_paths = pd.concat([datatype_folders, extra_folders])
68+
expected_paths = expected_paths.drop_duplicates(subset="path")
69+
70+
expected_paths = self.remove_path_before_rawdata(expected_paths.path)
71+
72+
return expected_paths
73+
74+
def make_pathtable_search_filter(self, sub_names, ses_names, datatype):
75+
"""
76+
Create a string of arguments to pass to pd.query() that will
77+
create the table of only transferred sub, ses and datatype.
78+
79+
Two arguments must be created, one of all sub / ses / datatypes
80+
and the other of all non sub/ non ses / non datatype
81+
folders. These must be handled separately as they are
82+
mutually exclusive.
83+
"""
84+
sub_ses_dtype_arguments = []
85+
extra_arguments = []
86+
87+
for sub in sub_names:
88+
if sub == "all_non_sub":
89+
extra_arguments += ["is_non_sub == True"]
90+
else:
91+
for ses in ses_names:
92+
if ses == "all_non_ses":
93+
extra_arguments += [
94+
f"(parent_sub == '{sub}' & is_non_ses == True)"
95+
]
96+
else:
97+
for dtype in datatype:
98+
if dtype == "all_non_datatype":
99+
extra_arguments += [
100+
f"(parent_sub == '{sub}' & parent_ses == '{ses}' "
101+
f"& is_ses_level_non_datatype == True)"
102+
]
103+
else:
104+
sub_ses_dtype_arguments += [
105+
f"(parent_sub == '{sub}' & parent_ses == '{ses}' "
106+
f"& (parent_datatype == '{dtype}' "
107+
f"| parent_datatype == '{dtype}'))"
108+
]
109+
110+
return sub_ses_dtype_arguments, extra_arguments
111+
112+
def remove_path_before_rawdata(self, list_of_paths):
113+
"""
114+
Remove the path to project files before the "rawdata" so
115+
they can be compared no matter where the project was stored
116+
(e.g. on a central server vs. local filesystem).
117+
"""
118+
cut_paths = []
119+
for path_ in list_of_paths:
120+
parts = Path(path_).parts
121+
cut_paths.append(Path(*parts[parts.index("rawdata") :]))
122+
return cut_paths
123+
124+
def query_table(self, pathtable, arguments):
125+
"""
126+
Search the table for arguments, return empty
127+
if arguments empty
128+
"""
129+
if any(arguments):
130+
folders = pathtable.query(" | ".join(arguments))
131+
else:
132+
folders = pd.DataFrame()
133+
return folders
134+
135+
def parse_arguments(self, pathtable, list_of_names, field):
136+
"""
137+
Replicate datashuttle name formatting by parsing
138+
"all" arguments and turning them into a list of all names,
139+
(subject or session), taken from the pathtable.
140+
"""
141+
if list_of_names in [["all"], [f"all_{field}"]]:
142+
entries = pathtable.query(f"parent_{field} != False")[
143+
f"parent_{field}"
144+
]
145+
entries = list(set(entries))
146+
if list_of_names == ["all"]:
147+
entries += (
148+
[f"all_non_{field}"]
149+
if field != "datatype"
150+
else ["all_non_datatype"]
151+
)
152+
list_of_names = entries
153+
return list_of_names
154+
155+
def create_all_pathtable_files(self, pathtable):
156+
"""
157+
Create the entire test project in the defined
158+
location (usually project's `local_path`).
159+
"""
160+
for i in range(pathtable.shape[0]):
161+
filepath = pathtable["base_folder"][i] / pathtable["path"][i]
162+
filepath.parents[0].mkdir(parents=True, exist_ok=True)
163+
test_utils.write_file(filepath, contents="test_entry")
164+
165+
def central_from_local(self, path_):
166+
return Path(str(copy.copy(path_)).replace("local", "central"))

0 commit comments

Comments
 (0)