"""Actual reader ASTs and real pickle envelopes; fake host storage, no devices.""" import ast import io from pathlib import Path import pickle import struct import sys import tempfile from types import ModuleType, SimpleNamespace from typing import cast import unittest from unittest.mock import patch class DType: def __init__(self, name, itemsize): self.name, self.itemsize = name, itemsize UCHAR, UINT32 = DType('uchar', 1), DType('uint32', 4) class Storage: def __init__(self): self.arenas, self.copies, self.source_views = [], [], [] def buffer(self, device, size, dtype, *, preallocate=False): value = View(bytearray(size * dtype.itemsize), size, dtype, 0, self, 'arena') self.arenas.append((device, size, dtype, preallocate, value)) return value def tensor(self, path): owner = self class FileTensor: def __getitem__(self, key): if key.stop is not None or key.step is not None: raise AssertionError('Unexpected file slice') raw = bytearray(path.read_bytes()[key.start:]) buffer = View(raw, len(raw), UCHAR, 0, owner, 'source') return SimpleNamespace(uop=SimpleNamespace(buffer=buffer)) return FileTensor() class View: def __init__(self, storage, size, dtype, offset, owner, kind): self.storage, self.size, self.dtype, self.offset = storage, size, dtype, offset self.owner, self.kind = owner, kind self.nbytes = size * dtype.itemsize def ensure_allocated(self): return self def view(self, size, dtype, offset): if offset < 0 or offset + size * dtype.itemsize > self.nbytes: raise AssertionError('Out of bounds fixture view') result = View(self.storage, size, dtype, self.offset + offset, self.owner, self.kind) if self.kind == 'source': self.owner.source_views.append((size, dtype.name, result.offset)) return result def copy_from(self, source): if self.nbytes != source.nbytes: raise AssertionError('Copy size mismatch') self.owner.copies.append((self.offset, source.offset, self.nbytes, self.dtype.name, source.dtype.name)) self.storage[self.offset:self.offset + self.nbytes] = source.storage[source.offset:source.offset + source.nbytes] return self def reader(path, name): node = next(item for item in ast.parse(path.read_text()).body if isinstance(item, ast.FunctionDef) and item.name == name) before = ast.dump(node, include_attributes=False) namespace = {'Path': Path, 'pathlib': SimpleNamespace(Path=Path), 'io': io, 'pickle': pickle, 'struct': struct, 'cast': cast} exec(compile(ast.Module(body=[node], type_ignores=[]), str(path), 'exec'), namespace) assert ast.dump(node, include_attributes=False) == before return namespace[name] def envelope(path, payload): markers = (object(), object(), object()) records = ((32, UINT32, (8 << 20) - 48), (19, UCHAR, len(payload) - 19), (32, UINT32, (8 << 20) - 48)) class OOBPickler(pickle.Pickler): def persistent_id(self, value): for marker, record in zip(markers, records, strict=True): if value is marker: return record return None stream = io.BytesIO() OOBPickler(stream).dump({'views': markers, 'metadata': {'abi': 'fixture', 'shape': (3, 7)}}) opcodes = stream.getvalue() path.write_bytes(struct.pack('