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