// Tests des Rettungs-Abbilds (kern/rip/rettung.ts, 5.5.0) mit einem // nachgebauten Gerät: heile Bereiche kommen 1:1, ein Sektor, der einmal // zickt, wird beim zweiten Versuch gelesen, was dauerhaft unlesbar ist, // wird mit Nullen gefüllt — und NUR das. Dazu Abbruch, unbekannte Größe, // der Bericht. import { describe, expect, it } from 'vitest' import { BLOCK_BYTES, SEKTOR_BYTES, rettungsAbbild, rettungsBericht, stelleHinzu, type UnlesbareStelle } from '../src/kern/rip/rettung' /** Ein Gerät mit `groesse` Bytes (Muster: Byte = Position mod 251), dauerhaft * kaputten Sektoren und solchen, die genau `zickt`-mal scheitern. */ function geraet(groesse: number, kaputt: number[], zickt: Record = {}) { const daten = Buffer.alloc(groesse) for (let i = 0; i < groesse; i++) daten[i] = i % 251 const versuche: Array<[number, number]> = [] const restZicken = { ...zickt } const leser = async (puffer: Buffer, laenge: number, position: number): Promise => { versuche.push([position, laenge]) const ersterSektor = Math.floor(position / SEKTOR_BYTES) const letzterSektor = Math.floor((position + laenge - 1) / SEKTOR_BYTES) for (let s = ersterSektor; s <= letzterSektor; s++) { if (kaputt.includes(s)) throw new Error(`I/O-Fehler Sektor ${s}`) if ((restZicken[s] ?? 0) > 0) { restZicken[s] -= 1 throw new Error(`zickt Sektor ${s}`) } } daten.copy(puffer, 0, position, position + laenge) return laenge } const teile: Buffer[] = [] const schreiber = async (puffer: Buffer, laenge: number): Promise => { teile.push(Buffer.from(puffer.subarray(0, laenge))) } return { daten, leser, schreiber, ausgabe: () => Buffer.concat(teile), versuche } } describe('rettungsAbbild', () => { it('eine heile Disc kommt 1:1 — ohne feines Lesen, ohne Nullen', async () => { const g = geraet(2 * BLOCK_BYTES + 5000, []) const e = await rettungsAbbild({ leser: g.leser, schreiber: g.schreiber, erwarteteBytes: g.daten.length }) expect(e.status).toBe('success') expect(e.bytes).toBe(g.daten.length) expect(e.unlesbareSektoren).toBe(0) expect(g.ausgabe().equals(g.daten)).toBe(true) // Drei Lesevorgänge: zwei volle Blöcke und der Rest. expect(g.versuche).toHaveLength(3) }) it('zwei dauerhaft kaputte Sektoren werden genullt — der Rest bleibt', async () => { const kaputt = [600, 601] // im zweiten Block (Block = 512 Sektoren) const g = geraet(3 * BLOCK_BYTES, kaputt) const fortschritte: number[] = [] const e = await rettungsAbbild({ leser: g.leser, schreiber: g.schreiber, erwarteteBytes: g.daten.length, aufFortschritt: (p) => fortschritte.push(p) }) expect(e.status).toBe('success') expect(e.unlesbareSektoren).toBe(2) expect(e.stellen).toEqual([{ offset: 600 * SEKTOR_BYTES, bytes: 2 * SEKTOR_BYTES }]) const erwartet = Buffer.from(g.daten) erwartet.fill(0, 600 * SEKTOR_BYTES, 602 * SEKTOR_BYTES) expect(g.ausgabe().equals(erwartet)).toBe(true) expect(fortschritte[fortschritte.length - 1]).toBe(100) }) it('ein Sektor, der einmal zickt, wird beim zweiten Versuch gelesen — keine Null', async () => { const g = geraet(BLOCK_BYTES, [], { 100: 1 }) const e = await rettungsAbbild({ leser: g.leser, schreiber: g.schreiber, erwarteteBytes: g.daten.length }) expect(e.status).toBe('success') expect(e.unlesbareSektoren).toBe(0) expect(g.ausgabe().equals(g.daten)).toBe(true) }) it('zickt er öfter als es Versuche gibt, wird er genullt', async () => { const g = geraet(BLOCK_BYTES, [], { 100: 10 }) const e = await rettungsAbbild({ leser: g.leser, schreiber: g.schreiber, erwarteteBytes: g.daten.length, versuche: 2 }) expect(e.unlesbareSektoren).toBe(1) expect(e.stellen).toEqual([{ offset: 100 * SEKTOR_BYTES, bytes: SEKTOR_BYTES }]) }) it('Abbruch: cancelled mit dem Stand bis dahin', async () => { const g = geraet(4 * BLOCK_BYTES, []) const steuerung = new AbortController() let geschrieben = 0 const schreiber = async (p: Buffer, n: number): Promise => { await g.schreiber(p, n) geschrieben += 1 if (geschrieben === 2) steuerung.abort() } const e = await rettungsAbbild({ leser: g.leser, schreiber, erwarteteBytes: g.daten.length, abbruch: steuerung.signal }) expect(e.status).toBe('cancelled') expect(e.bytes).toBe(2 * BLOCK_BYTES) }) it('unbekannte Größe: Fehler, kein Abbild', async () => { const g = geraet(10, []) const e = await rettungsAbbild({ leser: g.leser, schreiber: g.schreiber, erwarteteBytes: 0 }) expect(e.status).toBe('error') }) it('ein Schreibfehler bricht mit Stand ab', async () => { const g = geraet(2 * BLOCK_BYTES, []) const schreiber = async (): Promise => { throw new Error('ENOSPC') } const e = await rettungsAbbild({ leser: g.leser, schreiber, erwarteteBytes: g.daten.length }) expect(e.status).toBe('error') if (e.status === 'error') expect(e.fehler).toContain('ENOSPC') }) }) describe('stelleHinzu / rettungsBericht', () => { it('fasst angrenzende Bereiche zusammen', () => { const stellen: UnlesbareStelle[] = [] stelleHinzu(stellen, 0, 2048) stelleHinzu(stellen, 2048, 2048) stelleHinzu(stellen, 8192, 2048) expect(stellen).toEqual([ { offset: 0, bytes: 4096 }, { offset: 8192, bytes: 2048 }, ]) }) it('der Bericht nennt Stand, Sektoren, Anteil und die Stellen', () => { const text = rettungsBericht('G:', { status: 'success', bytes: 1_000_000, unlesbareSektoren: 2, stellen: [{ offset: 4096, bytes: 4096 }] }, 1_000_000) expect(text).toContain('Stand: vollständig') expect(text).toContain('Unlesbare Sektoren: 2 (0.4096 %) an 1 Stellen') expect(text).toContain('ab Byte 4096: 4096 Bytes genullt') }) })