Coverage for tests/unit_tests/utils/test_util.py: 100%
116 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9"""
10Tests of util.py
11"""
13import os
14from pathlib import Path
15import pytest
16from tests.conftest import ApioRunner
17from apio.common.debug_util import is_debug
18from apio.utils.util import (
19 get_apio_release_info,
20 plurality,
21 list_plurality,
22 pushd,
23 # subprocess_call,
24 compute_file_sha256,
25 resolve_packages_dir,
26)
28# TODO: Add more tests.
31def test_release_info():
32 """Tests that the release info placeholder is empty. This value
33 is set on the fly by build and publishing github workflows."""
34 version_info = get_apio_release_info()
35 assert isinstance(version_info, str)
36 assert version_info == ""
39def test_pluraliry():
40 """Tests the plurality() function."""
41 # -- Test for ints 1, 2, 3
42 assert plurality(1, "file") == "1 file"
43 assert plurality(2, "file") == "2 files"
44 assert plurality(3, "file") == "3 files"
46 # -- Test for lengths 1, 2, 3.
47 assert plurality(["aa"], "file") == "1 file"
48 assert plurality(["aa", "bb"], "file") == "2 files"
49 assert plurality(["aa", "bb", "cc"], "file") == "3 files"
52def test_list_pluraliry():
53 """Tests the list_plurality() function."""
55 # -- Test for lengths 1, 2, and 3.
56 assert list_plurality(["aa"], "or") == "aa"
57 assert list_plurality(["aa", "bb"], "and") == "aa and bb"
58 assert list_plurality(["aa", "bb", "cc"], "or") == "aa, bb, or cc"
60 # -- An empty list should trhow an assert exception.
61 with pytest.raises(AssertionError):
62 list_plurality([], "or")
65def test_is_debug():
66 """Tests the is_debug() function."""
68 # -- Assuming APIO_DEBUG is not defined.
69 assert not is_debug(1)
70 assert not is_debug(2)
71 assert not is_debug(3)
73 # -- Enter debug mode level 1.
74 os.environ["APIO_DEBUG"] = "1"
75 assert is_debug(1)
76 assert not is_debug(2)
77 assert not is_debug(3)
79 # -- Enter debug mode level 2.
80 os.environ["APIO_DEBUG"] = "2"
81 assert is_debug(1)
82 assert is_debug(2)
83 assert not is_debug(3)
85 # -- Enter debug mode level 3.
86 os.environ["APIO_DEBUG"] = "3"
87 assert is_debug(1)
88 assert is_debug(2)
89 assert is_debug(3)
91 # -- Exit debug mode
92 os.environ.pop("APIO_DEBUG")
93 assert not is_debug(1)
94 assert not is_debug(2)
95 assert not is_debug(3)
98def test_pushd(apio_runner: ApioRunner):
99 """Test the pushd context manager."""
101 with apio_runner.in_sandbox() as sb:
102 # -- Define dir 1.
103 dir1 = sb.proj_dir
104 assert dir1.is_dir()
106 # -- Define dir 2
107 dir2 = dir1 / "dir2"
108 assert dir2.resolve() != dir1.resolve()
109 dir2.mkdir()
111 # -- Change to dir1
112 os.chdir(dir1)
113 assert Path.cwd().resolve() == dir1.resolve()
115 # -- Pushd to dir 2
116 with pushd(dir2):
117 assert Path.cwd().resolve() == dir2.resolve()
119 # -- Back from pushd to dir1
120 assert Path.cwd().resolve() == dir1.resolve()
123def test_compute_file_sha256(apio_runner: ApioRunner):
124 """Test compute_file_sha256()."""
126 with apio_runner.in_sandbox() as sb:
128 path = Path("./my-test-file")
130 # -- Test with an empty file.
131 path = Path("./my-test-file1")
132 sb.write_file(path, "")
133 sha256 = compute_file_sha256(path)
134 assert (
135 sha256 == "e3b0c44298fc1c149afbf4c8996fb92427"
136 "ae41e4649b934ca495991b7852b855"
137 )
139 # -- Test with non empty file.
140 path = Path("./my-test-file2")
141 sb.write_file(path, "hello world")
142 sha256 = compute_file_sha256(path)
143 assert (
144 sha256 == "b94d27b9934d3e08a52e52d7da7dabfac"
145 "484efe37a5380ee9088f7ace2efcde9"
146 )
149def test_resolve_packages_dir_expanded_check(apio_runner: ApioRunner):
150 """Tests that the 'packages' safety check of APIO_PACKAGES is applied
151 to the value after the env vars expansion."""
153 with apio_runner.in_sandbox() as sb:
155 # -- The original var value contains 'packages' but the expanded value
156 # -- doesn't. Should fail.
157 os.environ["packages_dir"] = str(sb.home_dir / "my-tools")
158 os.environ["APIO_PACKAGES"] = "${packages_dir}"
159 with apio_runner.with_logger() as log:
160 with pytest.raises(SystemExit) as e:
161 resolve_packages_dir(sb.home_dir)
162 assert e.value.code == 1
163 assert "should include the strings 'apio' and 'packages'" in log.out
165 # -- The original var value doesn't contain 'packages' but the expanded value
166 # -- does. Should pass.
167 os.environ["my_dir"] = str(sb.home_dir / "my-packages")
168 os.environ["APIO_PACKAGES"] = "${my_dir}"
169 assert resolve_packages_dir(sb.home_dir) == (
170 sb.home_dir / "my-packages"
171 )
174def test_resolve_packages_dir_apio_and_packages_check(
175 apio_runner: ApioRunner,
176):
177 """Tests that APIO_PACKAGES must contain both 'apio' and 'packages',
178 case insensitive."""
180 with apio_runner.in_sandbox() as sb:
182 # -- Case 1: Contains 'packages' but not 'apio'. Should fail. The check
183 # -- happens before the dir is accessed so a fake path is ok.
184 os.environ["APIO_PACKAGES"] = str(Path("/no-such-dir/my-packages"))
185 with apio_runner.with_logger() as log:
186 with pytest.raises(SystemExit) as e:
187 resolve_packages_dir(sb.home_dir)
188 assert e.value.code == 1
189 assert "should include the strings 'apio' and 'packages'" in log.out
191 # -- Case 2: Contains 'apio' but not 'packages'. Should fail.
192 os.environ["APIO_PACKAGES"] = str(Path("/no-such-dir/my-apio-tools"))
193 with apio_runner.with_logger() as log:
194 with pytest.raises(SystemExit) as e:
195 resolve_packages_dir(sb.home_dir)
196 assert e.value.code == 1
197 assert "should include the strings 'apio' and 'packages'" in log.out
199 # -- Case 3: Contains neither 'apio' nor 'packages'. Should fail.
200 os.environ["APIO_PACKAGES"] = str(Path("/no-such-dir/my-tools"))
201 with apio_runner.with_logger() as log:
202 with pytest.raises(SystemExit) as e:
203 resolve_packages_dir(sb.home_dir)
204 assert e.value.code == 1
205 assert "should include the strings 'apio' and 'packages'" in log.out
207 # -- Case 4: Upper case 'PACKAGES' and 'APIO'. Should pass.
208 os.environ["APIO_PACKAGES"] = str(sb.home_dir / "APIO-PACKAGES")
209 assert resolve_packages_dir(sb.home_dir) == (
210 sb.home_dir / "APIO-PACKAGES"
211 )
214def test_resolve_packages_dir_dot_dot(apio_runner: ApioRunner):
215 """Tests that '..' path components in APIO_PACKAGES are rejected."""
217 with apio_runner.in_sandbox() as sb:
219 # -- Case 1: '..' that removes the 'apio' and 'packages' component.
220 # -- Should fail.
221 os.environ["APIO_PACKAGES"] = str(
222 Path("/no-such-dir/apio-packages/..")
223 )
224 with apio_runner.with_logger() as log:
225 with pytest.raises(SystemExit) as e:
226 resolve_packages_dir(sb.home_dir)
227 assert e.value.code == 1
228 assert "should not contain '..'" in log.out
230 # -- Case 2: '..' that keeps the 'apio' and 'packages' component.
231 # -- Should fail.
232 os.environ["APIO_PACKAGES"] = str(
233 Path("/no-such-dir/tmp/../apio-packages")
234 )
235 with apio_runner.with_logger() as log:
236 with pytest.raises(SystemExit) as e:
237 resolve_packages_dir(sb.home_dir)
238 assert e.value.code == 1
239 assert "should not contain '..'" in log.out
241 # -- Case 3: '..' inside a dir name is not a '..' component.
242 # -- Should pass.
243 os.environ["APIO_PACKAGES"] = str(sb.sandbox_dir / "apio..packages")
244 assert resolve_packages_dir(sb.home_dir) == (
245 sb.sandbox_dir / "apio..packages"
246 )