feat(v5): 5.5.0 — Kern und Oberfläche aufgeteilt, Übersicht und Einstellungen neu, Erst-Einrichtung mit Poster-Kulisse
Commander-Wünsche vom 02.09.2026 vor der Freigabe von 5.4.0 (die deshalb nie veröffentlicht wurde): Kern: kern/index.ts ist nur noch Bootstrap und Router; Dienste in kern/dienste/ (Einstellungen, Werkzeuge, Laufwerke, Erkennung, Vorgänge, Speicher, Benachrichtigung, Automatik), Kontext in kern/kontext.ts, Protokoll-Datei in gemeinsam/protokoll.ts, Platz-Rechnung in gemeinsam/platz.ts. Oberfläche: fenster/App.tsx ist nur noch der Rahmen; bausteine.tsx, dialoge/, uebersicht/ (LaufwerkKachel, KompressionsKarte, SpeicherKarte, VerlaufKarte), einstellungen/ (Karten je Funktion, SprachAuswahl), einrichtung/ (ErstEinrichtung, PosterHintergrund). Neu: Bibliothek weg, dafür Verlauf; Speicher-und-Bilanz-Karte (statfs); LED-Fußleiste; MakeMKV-Kopf in die Einstellungen; Sprach-Chips mit Dropdown statt Allgemeinem Preset; Karten Automatik/Benachrichtigung/Allgemein/Update; Durchsuchen bei Werkzeugen; Arbeitsordner lokal (Encode dort, dann in die Ablage); Platz-Prüfung vor dem Rip (Dialog und Kern); Automatik mit Countdown; Serien-Merker über Discs; Rettungs-Abbild mit Nullen nur nach gesehenen Lesefehlern (kern/rip/rettung.ts, Phase rettet, iso:-Quelle); Warteschlange pausieren; Discord/Telegram-Benachrichtigung; Protokoll-Datei je Tag; Fensterlage 80 % mittig, gemerkt; Erst-Einrichtung in acht Schritten vor dem Poster-Grid. Beweise: 413 Tests (neu: speicher, benachrichtigung, automatik, rettung, fensterlage, protokoll, kleinkram-550), drei Typprüfungen, Klick-Beweis mit 29 Schritten inklusive Erst-Einrichtung (zweiter Start mit RIPPY_START_BEREICH=einrichtung), Bilder beweise/klick/01…09. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6678c81592
commit
3dc6798c79
@@ -0,0 +1,120 @@
|
||||
// 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<number, number> = {}) {
|
||||
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<number> => {
|
||||
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<void> => {
|
||||
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<UnlesbareStelle[]>([{ 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<void> => {
|
||||
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<void> => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user