Skip to content

Commit b3800d9

Browse files
committed
test: add unit tests for ingester file functions
Part of #1569
1 parent 6dab5dc commit b3800d9

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import os
2+
import yaml
3+
import pytest
4+
from unittest.mock import patch
5+
from kernelCI_app.management.commands.helpers.file_utils import (
6+
load_tree_names,
7+
move_file_to_failed_dir,
8+
verify_dir,
9+
verify_spool_dirs,
10+
)
11+
from kernelCI_app.tests.unitTests.helpers.fixtures.file_utils_data import (
12+
TREES_PATH_TESTING,
13+
BASE_TREES_FILE,
14+
EXPECTED_PARSED_TREES_FILE,
15+
BASE_FILE_NAME,
16+
NONEXISTING_FILE_NAME,
17+
TESTING_FAILED_DIR,
18+
EXISTING_DIRECTORY,
19+
MISSING_DIRECTORY,
20+
DENIED_DIRECTORY,
21+
NOT_A_DIRECTORY,
22+
UNACCESSIBLE_DIRECTORY,
23+
SPOOL_DIR_TESTING,
24+
FAIL_SPOOL_SUBDIR,
25+
ARCHIVE_SPOOL_SUBDIR,
26+
)
27+
28+
29+
class TestLoadTreeNames:
30+
@patch("builtins.open")
31+
@patch("yaml.safe_load")
32+
def test_load_tree_names_with_default_file(self, mock_yaml_load, mock_open):
33+
"""Test load_tree_names using default TREES_FILE constant."""
34+
mock_yaml_data = BASE_TREES_FILE
35+
mock_yaml_load.return_value = mock_yaml_data
36+
37+
result = load_tree_names()
38+
39+
mock_open.assert_called_once_with("/app/trees.yaml", "r", encoding="utf-8")
40+
mock_yaml_load.assert_called_once()
41+
assert result == EXPECTED_PARSED_TREES_FILE
42+
43+
@patch("builtins.open")
44+
@patch("yaml.safe_load")
45+
def test_load_tree_names_with_valid_yaml(self, mock_yaml_load, mock_open):
46+
"""Test load_tree_names with valid YAML data."""
47+
mock_yaml_load.return_value = BASE_TREES_FILE
48+
49+
result = load_tree_names(TREES_PATH_TESTING)
50+
51+
mock_open.assert_called_once_with(TREES_PATH_TESTING, "r", encoding="utf-8")
52+
mock_yaml_load.assert_called_once()
53+
assert result == EXPECTED_PARSED_TREES_FILE
54+
55+
@patch("builtins.open")
56+
@patch("yaml.safe_load")
57+
def test_load_tree_names_with_empty_trees(self, mock_yaml_load, mock_open):
58+
"""Test load_tree_names with empty trees section."""
59+
empty_trees_file = {"trees": {}}
60+
mock_yaml_load.return_value = empty_trees_file
61+
62+
result = load_tree_names(TREES_PATH_TESTING)
63+
64+
mock_open.assert_called_once_with(TREES_PATH_TESTING, "r", encoding="utf-8")
65+
mock_yaml_load.assert_called_once()
66+
assert result == {}
67+
68+
@patch("builtins.open")
69+
@patch("yaml.safe_load")
70+
def test_load_tree_names_with_no_trees_key(self, mock_yaml_load, mock_open):
71+
"""Test load_tree_names with YAML data missing trees key."""
72+
wrong_trees_file = {"not_trees": "any_value"}
73+
mock_yaml_load.return_value = wrong_trees_file
74+
75+
result = load_tree_names(TREES_PATH_TESTING)
76+
77+
mock_open.assert_called_once_with(TREES_PATH_TESTING, "r", encoding="utf-8")
78+
mock_yaml_load.assert_called_once()
79+
assert result == {}
80+
81+
@patch("builtins.open")
82+
def test_load_tree_names_file_not_found(self, mock_open):
83+
"""Test load_tree_names when file doesn't exist."""
84+
mock_open.side_effect = FileNotFoundError("File not found")
85+
with pytest.raises(FileNotFoundError):
86+
load_tree_names("/nonexistent/trees.yaml")
87+
88+
@patch("builtins.open")
89+
@patch("yaml.safe_load")
90+
def test_load_tree_names_invalid_yaml(self, mock_yaml_load, _):
91+
"""Test load_tree_names with invalid YAML content."""
92+
mock_yaml_load.side_effect = yaml.YAMLError("Invalid YAML")
93+
with pytest.raises(yaml.YAMLError):
94+
load_tree_names(TREES_PATH_TESTING)
95+
96+
97+
class TestMoveFileToFailedDir:
98+
@patch("os.rename")
99+
@patch("os.path.basename")
100+
@patch("os.path.join")
101+
def test_move_file_to_failed_dir_success(
102+
self, mock_join, mock_basename, mock_rename
103+
):
104+
"""Test successful file move to failed directory."""
105+
mock_basename.return_value = BASE_FILE_NAME
106+
mock_join.return_value = f"{TESTING_FAILED_DIR}/{BASE_FILE_NAME}"
107+
108+
move_file_to_failed_dir(BASE_FILE_NAME, TESTING_FAILED_DIR)
109+
110+
mock_basename.assert_called_once_with(BASE_FILE_NAME)
111+
mock_join.assert_called_once_with(TESTING_FAILED_DIR, BASE_FILE_NAME)
112+
mock_rename.assert_called_once_with(BASE_FILE_NAME, mock_join.return_value)
113+
114+
@patch("os.rename")
115+
@patch("os.path.basename")
116+
@patch("os.path.join")
117+
@patch("kernelCI_app.management.commands.helpers.file_utils.logger")
118+
def test_move_file_to_failed_dir_file_not_found(
119+
self, mock_logger, mock_join, mock_basename, mock_rename
120+
):
121+
"""Test file move when source file doesn't exist."""
122+
mock_rename.side_effect = FileNotFoundError("File not found")
123+
mock_rename.return_value = NONEXISTING_FILE_NAME
124+
mock_join.return_value = f"{TESTING_FAILED_DIR}/{BASE_FILE_NAME}"
125+
126+
with pytest.raises(FileNotFoundError):
127+
move_file_to_failed_dir(NONEXISTING_FILE_NAME, TESTING_FAILED_DIR)
128+
129+
mock_logger.error.assert_called_once_with(
130+
"Error moving file %s to failed directory: %s",
131+
mock_rename.return_value,
132+
mock_rename.side_effect,
133+
)
134+
135+
136+
class TestVerifyDir:
137+
@patch("os.path.exists")
138+
@patch("os.path.isdir")
139+
@patch("os.access")
140+
@patch("kernelCI_app.management.commands.helpers.file_utils.logger")
141+
def test_verify_dir_valid_directory(
142+
self, mock_logger, mock_access, mock_isdir, mock_exists
143+
):
144+
"""Test verify_dir with valid, writable directory."""
145+
mock_access.return_value = True
146+
mock_isdir.return_value = True
147+
mock_exists.return_value = True
148+
149+
verify_dir(EXISTING_DIRECTORY)
150+
151+
mock_exists.assert_called_once_with(EXISTING_DIRECTORY)
152+
mock_isdir.assert_called_once_with(EXISTING_DIRECTORY)
153+
mock_access.assert_called_once_with(EXISTING_DIRECTORY, os.W_OK)
154+
mock_logger.info.assert_called_once_with(
155+
"Directory %s is valid and writable", EXISTING_DIRECTORY
156+
)
157+
158+
@patch("os.path.exists")
159+
@patch("os.makedirs")
160+
@patch("os.path.isdir")
161+
@patch("os.access")
162+
@patch("kernelCI_app.management.commands.helpers.file_utils.logger")
163+
def test_verify_dir_creates_missing_directory(
164+
self, mock_logger, mock_access, mock_isdir, mock_makedirs, mock_exists
165+
):
166+
"""Test verify_dir creates valid but missing directory."""
167+
mock_exists.return_value = False
168+
mock_access.return_value = True
169+
mock_isdir.return_value = True
170+
171+
verify_dir(MISSING_DIRECTORY)
172+
173+
mock_exists.assert_called_once_with(MISSING_DIRECTORY)
174+
mock_makedirs.assert_called_once_with(MISSING_DIRECTORY)
175+
mock_logger.info.assert_any_call("Directory %s created", MISSING_DIRECTORY)
176+
mock_logger.info.assert_any_call(
177+
"Directory %s is valid and writable", MISSING_DIRECTORY
178+
)
179+
180+
@patch("os.path.exists")
181+
@patch("os.makedirs")
182+
@patch("kernelCI_app.management.commands.helpers.file_utils.logger")
183+
def test_verify_dir_fails_to_create_directory(
184+
self, mock_logger, mock_makedirs, mock_exists
185+
):
186+
"""Test verify_dir when directory creation fails."""
187+
mock_makedirs.side_effect = PermissionError("Permission denied")
188+
mock_exists.return_value = False
189+
190+
with pytest.raises(PermissionError):
191+
verify_dir(DENIED_DIRECTORY)
192+
193+
mock_exists.assert_called_once_with(DENIED_DIRECTORY)
194+
mock_makedirs.assert_called_once_with(DENIED_DIRECTORY)
195+
assert mock_logger.error.call_count == 2
196+
mock_logger.error.assert_any_call(
197+
"Directory %s does not exist", DENIED_DIRECTORY
198+
)
199+
mock_logger.error.assert_any_call(
200+
"Error creating directory %s: %s",
201+
DENIED_DIRECTORY,
202+
mock_makedirs.side_effect,
203+
)
204+
205+
@patch("os.path.exists")
206+
@patch("os.path.isdir")
207+
def test_verify_dir_path_is_not_directory(self, mock_isdir, mock_exists):
208+
"""Test verify_dir when path exists but is not a directory."""
209+
mock_isdir.return_value = False
210+
mock_exists.return_value = True
211+
212+
with pytest.raises(
213+
Exception, match=f"Directory {NOT_A_DIRECTORY} is not a directory"
214+
):
215+
verify_dir(NOT_A_DIRECTORY)
216+
217+
mock_exists.assert_called_once_with(NOT_A_DIRECTORY)
218+
mock_isdir.assert_called_once_with(NOT_A_DIRECTORY)
219+
220+
@patch("os.path.exists")
221+
@patch("os.path.isdir")
222+
@patch("os.access")
223+
def test_verify_dir_not_writable(self, mock_access, mock_isdir, mock_exists):
224+
"""Test verify_dir when directory is not writable."""
225+
mock_access.return_value = False
226+
mock_isdir.return_value = True
227+
mock_exists.return_value = True
228+
229+
with pytest.raises(
230+
Exception, match=f"Directory {UNACCESSIBLE_DIRECTORY} is not writable"
231+
):
232+
verify_dir(UNACCESSIBLE_DIRECTORY)
233+
234+
mock_exists.assert_called_once_with(UNACCESSIBLE_DIRECTORY)
235+
mock_isdir.assert_called_once_with(UNACCESSIBLE_DIRECTORY)
236+
mock_access.assert_called_once_with(UNACCESSIBLE_DIRECTORY, os.W_OK)
237+
238+
239+
class TestVerifySpoolDirs:
240+
@patch("kernelCI_app.management.commands.helpers.file_utils.verify_dir")
241+
@patch("os.path.join")
242+
def test_verify_spool_dirs_success(self, mock_join, mock_verify_dir):
243+
"""Test verify_spool_dirs with successful directory verification."""
244+
joined_fail_dir = "/".join([SPOOL_DIR_TESTING, FAIL_SPOOL_SUBDIR])
245+
joined_archive_dir = "/".join([SPOOL_DIR_TESTING, ARCHIVE_SPOOL_SUBDIR])
246+
mock_join.side_effect = [joined_fail_dir, joined_archive_dir]
247+
248+
verify_spool_dirs(SPOOL_DIR_TESTING)
249+
250+
assert mock_join.call_count == 2
251+
mock_join.assert_any_call(SPOOL_DIR_TESTING, FAIL_SPOOL_SUBDIR)
252+
mock_join.assert_any_call(SPOOL_DIR_TESTING, ARCHIVE_SPOOL_SUBDIR)
253+
254+
assert mock_verify_dir.call_count == 3
255+
mock_verify_dir.assert_any_call(SPOOL_DIR_TESTING)
256+
mock_verify_dir.assert_any_call(joined_fail_dir)
257+
mock_verify_dir.assert_any_call(joined_archive_dir)
258+
259+
@patch("kernelCI_app.management.commands.helpers.file_utils.verify_dir")
260+
@patch("os.path.join")
261+
def test_verify_spool_dirs_join_failure(self, mock_join, mock_verify_dir):
262+
"""Test verify_spool_dirs when os.path.join fails."""
263+
mock_join.side_effect = OSError("Join operation failed")
264+
265+
with pytest.raises(OSError):
266+
verify_spool_dirs(SPOOL_DIR_TESTING)
267+
268+
mock_join.assert_called_once_with(SPOOL_DIR_TESTING, FAIL_SPOOL_SUBDIR)
269+
mock_verify_dir.assert_not_called()
270+
271+
@patch("kernelCI_app.management.commands.helpers.file_utils.verify_dir")
272+
@patch("os.path.join")
273+
def test_verify_spool_dirs_verify_spool_dir_fails(self, mock_join, mock_verify_dir):
274+
"""Test verify_spool_dirs when spool directory verification fails."""
275+
joined_fail_dir = "/".join([SPOOL_DIR_TESTING, FAIL_SPOOL_SUBDIR])
276+
joined_archive_dir = "/".join([SPOOL_DIR_TESTING, ARCHIVE_SPOOL_SUBDIR])
277+
mock_join.side_effect = [joined_fail_dir, joined_archive_dir]
278+
279+
# Meant to represent any kind of failure in verify_dir
280+
mock_verify_dir.side_effect = Exception("Spool directory verification failed")
281+
282+
with pytest.raises(Exception, match="Spool directory verification failed"):
283+
verify_spool_dirs(SPOOL_DIR_TESTING)
284+
285+
assert mock_join.call_count == 2
286+
mock_join.assert_any_call(SPOOL_DIR_TESTING, FAIL_SPOOL_SUBDIR)
287+
mock_join.assert_any_call(SPOOL_DIR_TESTING, ARCHIVE_SPOOL_SUBDIR)
288+
289+
mock_verify_dir.assert_called_once_with(SPOOL_DIR_TESTING)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
TREES_PATH_TESTING = "/test/trees.yaml"
2+
3+
mainline_url = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"
4+
next_url = "https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git"
5+
stable_url = "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git"
6+
7+
BASE_TREES_FILE = {
8+
"trees": {
9+
"mainline": {"url": mainline_url},
10+
"next": {"url": next_url},
11+
"stable": {"url": stable_url},
12+
}
13+
}
14+
15+
EXPECTED_PARSED_TREES_FILE = {
16+
mainline_url: "mainline",
17+
next_url: "next",
18+
stable_url: "stable",
19+
}
20+
21+
22+
BASE_FILE_NAME = "test_file.json"
23+
NONEXISTING_FILE_NAME = "nonexistent_file.json"
24+
TESTING_FAILED_DIR = "/failed/dir"
25+
26+
EXISTING_DIRECTORY = "/existing/directory"
27+
MISSING_DIRECTORY = "/missing/directory"
28+
DENIED_DIRECTORY = "/denied/directory"
29+
NOT_A_DIRECTORY = "/not/a/directory.file"
30+
UNACCESSIBLE_DIRECTORY = "/unaccessible/directory"
31+
32+
SPOOL_DIR_TESTING = "/spool"
33+
FAIL_SPOOL_SUBDIR = "failed"
34+
ARCHIVE_SPOOL_SUBDIR = "archive"

0 commit comments

Comments
 (0)