4from abc
import ABC, abstractmethod
5from functools
import total_ordering
6from pathlib
import Path
7from typing
import Generic, Optional, TypeVar, cast
10from .Literals
import LiteralString, LiteralTodoStub, LiteralValue, TodoStub
11from .SourceFile
import SourceFile
12from .TypedPath
import TypedPath
20 def __init__(self, file_name: Optional[str], line: int):
35 def ide_link(self, _:
"SnapshotFileLayout") -> str:
36 return f
"File: {self._file_name}, Line: {self._line}"
39 if not isinstance(other, CallLocation):
49 if not isinstance(other, CallLocation):
54 if not isinstance(other, CallLocation):
63 def __init__(self, location: CallLocation, rest_of_stack: list[CallLocation]):
67 def ide_link(self, layout:
"SnapshotFileLayout") -> str:
71 return "\n".join(links)
74 if not isinstance(other, CallStack):
94 file_path = call.file_name
96 raise ValueError(
"No file path available in CallLocation.")
97 return TypedPath(os.path.abspath(Path(file_path)))
100def recordCall(callerFileOnly: bool) -> CallStack:
101 stack_frames_raw = inspect.stack()
102 first_real_frame = next(
105 for i, x
in enumerate(stack_frames_raw)
106 if x.frame.f_globals.get(
"__package__") != __package__
111 stack_frames = stack_frames_raw[first_real_frame:]
114 caller_file = stack_frames[0].filename
116 frame
for frame
in stack_frames
if frame.filename == caller_file
120 CallLocation(frame.filename, frame.lineno)
for frame
in stack_frames
123 location = call_locations[0]
124 rest_of_stack = call_locations[1:]
126 return CallStack(location, rest_of_stack)
130 def __init__(self, snapshot: U, call_stack: CallStack):
138 self.writes: dict[T, FirstWrite[U]] = {}
145 layout: SnapshotFileLayout,
146 allow_multiple_equivalent_writes: bool =
True,
150 if key
not in self.writes:
151 self.writes[key] = this_write
154 existing = self.writes[key]
155 if existing.snapshot != snapshot:
157 f
"Snapshot was set to multiple values!\n first time: {existing.call_stack.location.ide_link(layout)}\n this time: {call.location.ide_link(layout)}"
159 elif not allow_multiple_equivalent_writes:
160 raise ValueError(
"Snapshot was set to the same value multiple times.")
164 def record(self, key: T, snapshot: U, call: CallStack, layout: SnapshotFileLayout):
174 snapshot: LiteralValue,
176 layout: SnapshotFileLayout,
180 file = layout.sourcefile_for_call(call.location)
183 snapshot.expected
is not None
184 and isinstance(snapshot.expected, str)
185 and isinstance(snapshot.format, LiteralString)
187 content =
SourceFile(file.name, layout.fs.file_read(file))
189 snapshot = cast(LiteralValue, snapshot)
190 parsed_value = content.parse_to_be_like(
192 ).parse_literal(snapshot.format)
193 except Exception
as e:
194 raise AssertionError(
195 f
"Error while parsing the literal at {call.location.ide_link(layout)}. Please report this error at https://github.com/diffplug/selfie"
197 if parsed_value != snapshot.expected:
198 raise layout.fs.assert_failed(
199 f
"Selfie cannot modify the literal at {call.location.ide_link(layout)} because Selfie has a parsing bug. Please report this error at https://github.com/diffplug/selfie",
210 sorted_writes = sorted(
212 key=
lambda x: (x.call_stack.location.file_name, x.call_stack.location.line),
216 first_write = sorted_writes[0]
217 current_file = layout.sourcefile_for_call(first_write.call_stack.location)
218 content =
SourceFile(current_file.name, layout.fs.file_read(current_file))
219 delta_line_numbers = 0
221 for write
in sorted_writes:
223 file_path = layout.sourcefile_for_call(write.call_stack.location)
225 if file_path != current_file:
226 layout.fs.file_write(current_file, content.as_string)
227 current_file = file_path
229 current_file.name, layout.fs.file_read(current_file)
231 delta_line_numbers = 0
234 line = write.call_stack.location.line + delta_line_numbers
235 if isinstance(write.snapshot.format, LiteralTodoStub):
236 kind: TodoStub = write.snapshot.actual
237 content.replace_on_line(line, f
".{kind.name}_TODO(", f
".{kind.name}(")
239 to_be_literal = content.parse_to_be_like(line)
241 literal_change = to_be_literal.set_literal_and_get_newline_delta(
244 delta_line_numbers += literal_change
247 layout.fs.file_write(current_file, content.as_string)
251 def __init__(self, location: TypedPath, layout: SnapshotFileLayout, data: bytes):
257 if self.
data is None:
258 raise Exception(
"Data has already been written to disk")
263 if self.
data is not None:
268 if not isinstance(other, ToBeFileLazyBytes):
270 return self.
readData() == other.readData()
285 layout: SnapshotFileLayout,
289 lazyBytes.writeToDisk()
bool same_path_as(self, "CallLocation" other)
str source_filename_without_extension(self)
__init__(self, Optional[str] file_name, int line)
str ide_link(self, "SnapshotFileLayout" _)
"CallLocation" with_line(self, int line)
Optional[str] file_name(self)
str ide_link(self, "SnapshotFileLayout" layout)
__init__(self, CallLocation location, list[CallLocation] rest_of_stack)
record(self, T key, U snapshot, CallStack call, SnapshotFileLayout layout)
__init__(self, U snapshot, CallStack call_stack)
persist_writes(self, SnapshotFileLayout layout)
record(self, LiteralValue snapshot, CallStack call, SnapshotFileLayout layout)
TypedPath sourcefile_for_call(self, CallLocation call)
TypedPath root_folder(self)
__init__(self, TypedPath location, SnapshotFileLayout layout, bytes data)
None writeToDisk(self, TypedPath key, bytes snapshot, CallStack call, SnapshotFileLayout layout)
recordInternal(self, T key, U snapshot, CallStack call, SnapshotFileLayout layout, bool allow_multiple_equivalent_writes=True)