xref: /aosp_15_r20/external/pigweed/pw_ide/py/test_cases.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""pw_ide test classes."""
15
16from contextlib import contextmanager
17from io import TextIOWrapper
18from pathlib import Path
19import tempfile
20from typing import Generator
21import unittest
22
23from pw_ide.settings import PigweedIdeSettings
24
25
26class TempDirTestCase(unittest.TestCase):
27    """Run tests that need access to a temporary directory."""
28
29    def setUp(self) -> None:
30        self.temp_dir = tempfile.TemporaryDirectory()
31        self.temp_dir_path = Path(self.temp_dir.name)
32
33    def tearDown(self) -> None:
34        self.temp_dir.cleanup()
35        return super().tearDown()
36
37    @contextmanager
38    def make_temp_file(
39        self, filename: Path | str, content: str = ''
40    ) -> Generator[tuple[TextIOWrapper, Path], None, None]:
41        """Create a temp file in the test case's temp dir.
42
43        Returns a tuple containing the file reference and the file's path.
44        The file can be read immediately.
45        """
46        path = self.temp_dir_path / filename
47
48        with open(path, 'a+', encoding='utf-8') as file:
49            file.write(content)
50            file.flush()
51            file.seek(0)
52            yield (file, path)
53
54    def touch_temp_file(self, filename: Path | str, content: str = '') -> None:
55        """Create a temp file in the test case's temp dir, without context."""
56        with self.make_temp_file(filename, content):
57            pass
58
59    @contextmanager
60    def open_temp_file(
61        self,
62        filename: Path | str,
63    ) -> Generator[tuple[TextIOWrapper, Path], None, None]:
64        """Open an existing temp file in the test case's temp dir.
65
66        Returns a tuple containing the file reference and the file's path.
67        """
68        path = self.temp_dir_path / filename
69
70        with open(path, 'r', encoding='utf-8') as file:
71            yield (file, path)
72
73    @contextmanager
74    def make_temp_files(
75        self, files_data: list[tuple[Path | str, str]]
76    ) -> Generator[list[TextIOWrapper], None, None]:
77        """Create several temp files in the test case's temp dir.
78
79        Provide a list of file name and content tuples. Saves you the trouble
80        of excessive `with self.make_temp_file, self.make_temp_file...`
81        nesting, and allows programmatic definition of multiple temp file
82        contexts. Files can be read immediately.
83        """
84        files: list[TextIOWrapper] = []
85
86        for filename, content in files_data:
87            file = open(self.path_in_temp_dir(filename), 'a+', encoding='utf-8')
88            file.write(content)
89            file.flush()
90            file.seek(0)
91            files.append(file)
92
93        yield files
94
95        for file in files:
96            file.close()
97
98    def touch_temp_files(
99        self, files_data: list[tuple[Path | str, str]]
100    ) -> None:
101        """Create several temp files in the temp dir, without context."""
102        with self.make_temp_files(files_data):
103            pass
104
105    @contextmanager
106    def open_temp_files(
107        self, files_data: list[Path | str]
108    ) -> Generator[list[TextIOWrapper], None, None]:
109        """Open several existing temp files in the test case's temp dir.
110
111        Provide a list of file names. Saves you the trouble of excessive
112        `with self.open_temp_file, self.open_temp_file...` nesting, and allows
113        programmatic definition of multiple temp file contexts.
114        """
115        files: list[TextIOWrapper] = []
116
117        for filename in files_data:
118            file = open(self.path_in_temp_dir(filename), 'r', encoding='utf-8')
119            files.append(file)
120
121        yield files
122
123        for file in files:
124            file.close()
125
126    def path_in_temp_dir(self, path: Path | str) -> Path:
127        """Place a path into the test case's temp dir.
128
129        This only works with a relative path; with an absolute path, this is a
130        no-op.
131        """
132        return self.temp_dir_path / path
133
134    def paths_in_temp_dir(self, *paths: Path | str) -> list[Path]:
135        """Place several paths into the test case's temp dir.
136
137        This only works with relative paths; with absolute paths, this is a
138        no-op.
139        """
140        return [self.path_in_temp_dir(path) for path in paths]
141
142
143class PwIdeTestCase(TempDirTestCase):
144    """A test case for testing `pw_ide`.
145
146    Provides a temp dir for testing file system actions and access to IDE
147    settings that wrap the temp dir.
148    """
149
150    def make_ide_settings(
151        self,
152        working_dir: str | Path | None = None,
153        targets_include: list[str] | None = None,
154        targets_exclude: list[str] | None = None,
155        cascade_targets: bool = False,
156    ) -> PigweedIdeSettings:
157        """Make settings that wrap provided paths in the temp path."""
158
159        if working_dir is not None:
160            working_dir_path = self.path_in_temp_dir(working_dir)
161        else:
162            working_dir_path = self.temp_dir_path
163
164        if targets_include is None:
165            targets_include = []
166
167        if targets_exclude is None:
168            targets_exclude = []
169
170        return PigweedIdeSettings(
171            False,
172            False,
173            False,
174            default_config={
175                'working_dir': str(working_dir_path),
176                'targets_include': targets_include,
177                'targets_exclude': targets_exclude,
178                'cascade_targets': cascade_targets,
179            },
180        )
181