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, List, Optional, Tuple, Union 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: Union[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( 55 self, filename: Union[Path, str], content: str = '' 56 ) -> None: 57 """Create a temp file in the test case's temp dir, without context.""" 58 with self.make_temp_file(filename, content): 59 pass 60 61 @contextmanager 62 def open_temp_file( 63 self, 64 filename: Union[Path, str], 65 ) -> Generator[Tuple[TextIOWrapper, Path], None, None]: 66 """Open an existing temp file in the test case's temp dir. 67 68 Returns a tuple containing the file reference and the file's path. 69 """ 70 path = self.temp_dir_path / filename 71 72 with open(path, 'r', encoding='utf-8') as file: 73 yield (file, path) 74 75 @contextmanager 76 def make_temp_files( 77 self, files_data: List[Tuple[Union[Path, str], str]] 78 ) -> Generator[List[TextIOWrapper], None, None]: 79 """Create several temp files in the test case's temp dir. 80 81 Provide a list of file name and content tuples. Saves you the trouble 82 of excessive `with self.make_temp_file, self.make_temp_file...` 83 nesting, and allows programmatic definition of multiple temp file 84 contexts. Files can be read immediately. 85 """ 86 files: List[TextIOWrapper] = [] 87 88 for filename, content in files_data: 89 file = open(self.path_in_temp_dir(filename), 'a+', encoding='utf-8') 90 file.write(content) 91 file.flush() 92 file.seek(0) 93 files.append(file) 94 95 yield files 96 97 for file in files: 98 file.close() 99 100 def touch_temp_files( 101 self, files_data: List[Tuple[Union[Path, str], str]] 102 ) -> None: 103 """Create several temp files in the temp dir, without context.""" 104 with self.make_temp_files(files_data): 105 pass 106 107 @contextmanager 108 def open_temp_files( 109 self, files_data: List[Union[Path, str]] 110 ) -> Generator[List[TextIOWrapper], None, None]: 111 """Open several existing temp files in the test case's temp dir. 112 113 Provide a list of file names. Saves you the trouble of excessive 114 `with self.open_temp_file, self.open_temp_file...` nesting, and allows 115 programmatic definition of multiple temp file contexts. 116 """ 117 files: List[TextIOWrapper] = [] 118 119 for filename in files_data: 120 file = open(self.path_in_temp_dir(filename), 'r', encoding='utf-8') 121 files.append(file) 122 123 yield files 124 125 for file in files: 126 file.close() 127 128 def path_in_temp_dir(self, path: Union[Path, str]) -> Path: 129 """Place a path into the test case's temp dir. 130 131 This only works with a relative path; with an absolute path, this is a 132 no-op. 133 """ 134 return self.temp_dir_path / path 135 136 def paths_in_temp_dir(self, *paths: Union[Path, str]) -> List[Path]: 137 """Place several paths into the test case's temp dir. 138 139 This only works with relative paths; with absolute paths, this is a 140 no-op. 141 """ 142 return [self.path_in_temp_dir(path) for path in paths] 143 144 145class PwIdeTestCase(TempDirTestCase): 146 """A test case for testing `pw_ide`. 147 148 Provides a temp dir for testing file system actions and access to IDE 149 settings that wrap the temp dir. 150 """ 151 152 def make_ide_settings( 153 self, 154 working_dir: Optional[Union[str, Path]] = None, 155 targets: Optional[List[str]] = None, 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 is None: 165 targets = [] 166 167 return PigweedIdeSettings( 168 False, 169 False, 170 False, 171 default_config={ 172 'working_dir': str(working_dir_path), 173 'targets': targets, 174 }, 175 ) 176