lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests tools aufraeumen.test.ts

5,6 KB Rohdatei
tests/tools/aufraeumen.test.ts — 130 Zeilen
1 import {
2 copyFileSync,
3 existsSync,
4 mkdirSync,
5 mkdtempSync,
6 readFileSync,
7 rmSync,
8 writeFileSync,
9 } from 'node:fs';
10 import { spawnSync } from 'node:child_process';
11 import os from 'node:os';
12 import path from 'node:path';
13 import { afterEach, beforeEach, describe, expect, it } from 'vitest';
14
15 /*
16 * Befund 13 zur Fassung 5.10.0.
17 *
18 * `npm run clean` entfernte neben dem jederzeit neu baubaren `build/` auch
19 * `release/` - rekursiv, mit force, ohne Rueckfrage und ohne Ausnahme fuer die
20 * Dateien PRUEFSUMMEN-<Fassung>.txt.
21 *
22 * `release/` steht in .gitignore und ist damit die einzige Kopie. Die
23 * Pruefsummendateien sind ausdruecklich als Nachweis gedacht: "Ein Pruefer muss
24 * sagen koennen, mit welchem Programmstand eine Unterlage erstellt wurde"
25 * (scripts/pruefsummen.mjs). Wiederherstellbar sind sie nicht: Ein Neubau aus
26 * derselben Marke ergibt zwar denselben Quelltext, aber eine andere Pruefsumme.
27 * Und nach dem Loeschen kann `npm run pruefsummen` gar nichts mehr schreiben:
28 * Es braucht das Paket, an das es die Summe haengt.
29 *
30 * Die beiden Skripte widersprachen sich damit: pruefsummen.mjs behandelt
31 * `release/` als Archiv ("Im Ordner liegen erfahrungsgemaess auch aeltere
32 * Staende"), clean.mjs behandelte denselben Ordner als Zwischenstand.
33 *
34 * WARUM DER LAUF IN EINEM EIGENEN VERZEICHNIS STATTFINDET: Das Skript ist ein
35 * ausfuehrbares Modul ohne Ausfuhren und bildet seine Ziele aus dem eigenen Ort
36 * (`new URL('../build', import.meta.url)`). Geprueft wird deshalb das Skript
37 * selbst, mit einer Nachbildung des Projektordners im Kratzbereich - der
38 * Projektbaum und sein echtes `release/` werden nicht angefasst.
39 *
40 * WARUM DER AUFRUF MIT DOPPELTEM STRICH GENANNT SEIN MUSS: Der dokumentierte
41 * Einstiegspunkt ist `npm run clean` (package.json). npm nimmt ein
42 * unbekanntes Argument als eigene Einstellung und reicht es nicht weiter:
43 * `npm run clean --auch-release` erreicht das Skript nicht und laesst
44 * `release/` vollstaendig liegen, erst `npm run clean -- --auch-release` wirkt.
45 * Ausgabe und Kopfkommentar muessen deshalb den Aufruf nennen, der wirkt -
46 * sonst tippt den Befehl zweimal vergeblich, wer der eigenen Ausgabe folgt.
47 */
48
49 /** Der wirksame Aufruf ueber den dokumentierten Einstiegspunkt. */
50 const AUFRUF = 'npm run clean -- --auch-release';
51
52 const PRUEFSUMMEN = 'PRUEFSUMMEN-9.9.9.txt';
53 const PAKET = 'LSA-Planer-Setup-9.9.9.exe';
54
55 let wurzel = '';
56
57 beforeEach(() => {
58 wurzel = mkdtempSync(path.join(os.tmpdir(), 'lsa-clean-'));
59 mkdirSync(path.join(wurzel, 'scripts'));
60 mkdirSync(path.join(wurzel, 'build', 'assets'), { recursive: true });
61 mkdirSync(path.join(wurzel, 'release'));
62 writeFileSync(path.join(wurzel, 'build', 'index.html'), 'Platzhalter statt eines Baus', 'utf8');
63 writeFileSync(path.join(wurzel, 'build', 'assets', 'haupt.js'), 'Platzhalter', 'utf8');
64 writeFileSync(path.join(wurzel, 'release', PAKET), 'Platzhalter statt eines Pakets', 'utf8');
65 writeFileSync(path.join(wurzel, 'release', PRUEFSUMMEN), 'Platzhalter statt Summen', 'utf8');
66 copyFileSync('scripts/clean.mjs', path.join(wurzel, 'scripts', 'clean.mjs'));
67 });
68
69 afterEach(() => {
70 if (wurzel !== '') rmSync(wurzel, { recursive: true, force: true });
71 wurzel = '';
72 });
73
74 /** Laesst das Skript in der Nachbildung laufen und gibt seine Ausgabe zurueck. */
75 function lauf(...argumente: string[]): string {
76 const ergebnis = spawnSync(
77 process.execPath,
78 [path.join(wurzel, 'scripts', 'clean.mjs'), ...argumente],
79 { encoding: 'utf8' },
80 );
81 expect(ergebnis.status, `Lauf mit [${argumente.join(' ')}]: ${ergebnis.stderr}`).toBe(0);
82 return ergebnis.stdout;
83 }
84
85 /** Liegt die Datei noch im nachgebildeten release-Ordner? */
86 function imArchiv(name: string): boolean {
87 return existsSync(path.join(wurzel, 'release', name));
88 }
89
90 describe('npm run clean - was es entfernen darf', () => {
91 it('entfernt den Bauordner', () => {
92 lauf();
93 expect(existsSync(path.join(wurzel, 'build')), 'build/ nach dem Lauf').toBe(false);
94 });
95
96 it('laesst den Auslieferungsordner ohne ausdrueckliche Ansage unberuehrt', () => {
97 lauf();
98 expect(imArchiv(PAKET), 'Paket nach dem Lauf').toBe(true);
99 expect(imArchiv(PRUEFSUMMEN), 'Pruefsummendatei nach dem Lauf').toBe(true);
100 });
101
102 it('sagt in der Ausgabe, dass der Auslieferungsordner liegen bleibt', () => {
103 // Wer aufraeumen wollte, soll nicht raten muessen, was liegen blieb - und
104 // erfahren, wie er ihn doch loswird. Genannt sein muss der Aufruf, der
105 // wirklich wirkt (siehe Kopf dieser Datei), nicht nur der blosse Schalter.
106 const ausgabe = lauf();
107 expect(ausgabe, 'Ausgabe des Laufs').toContain('behalten: release');
108 expect(ausgabe, 'Hinweis auf den wirksamen Aufruf').toContain(AUFRUF);
109 });
110
111 it('nennt im Kopfkommentar denselben Aufruf wie in der Ausgabe', () => {
112 // Zusagen in Kommentaren sind Teil des Programms: Der Kopf darf nicht den
113 // Aufruf nennen, der ueber `npm run` verpufft.
114 const kopf = readFileSync('scripts/clean.mjs', 'utf8').replace(/\s+/g, ' ');
115 expect(kopf, 'Kopfkommentar von scripts/clean.mjs').toContain(AUFRUF);
116 });
117
118 it('entfernt mit --auch-release die Pakete, aber nie die Pruefsummen', () => {
119 lauf('--auch-release');
120 expect(imArchiv(PAKET), 'Paket nach dem Lauf mit --auch-release').toBe(false);
121 expect(imArchiv(PRUEFSUMMEN), 'Pruefsummendatei nach dem Lauf mit --auch-release').toBe(true);
122 });
123
124 it('kommt ohne vorhandenen Auslieferungsordner zurecht', () => {
125 // Ein frischer Klon hat kein release/ - der Lauf darf daran nicht brechen.
126 rmSync(path.join(wurzel, 'release'), { recursive: true, force: true });
127 lauf('--auch-release');
128 expect(existsSync(path.join(wurzel, 'build')), 'build/ nach dem Lauf').toBe(false);
129 });
130 });