Simple script and config (type-safe) for building custom Linux kernels for Firecracker MicroVMs
1// Deno native test runner
2// Run with: deno test --allow-read deno-test.ts
3
4import { assert, assertEquals, assertExists, assertThrows } from "@std/assert";
5import {
6 KernelConfigDeserializer,
7 KernelConfigParser,
8 KernelConfigSerializer,
9 type SerializeOptions,
10} from "./config.ts";
11
12// Test data
13const simpleConfig = `
14CONFIG_64BIT=y
15CONFIG_SMP=y
16CONFIG_NR_CPUS=64
17# CONFIG_DEBUG is not set
18CONFIG_LOCALVERSION="-test"
19`;
20
21const complexConfig = `
22#
23# Automatically generated file; DO NOT EDIT.
24# Linux/x86 6.6.100 Kernel Configuration
25#
26CONFIG_CC_VERSION_TEXT="gcc 13.3.0"
27CONFIG_64BIT=y
28
29#
30# General setup
31#
32CONFIG_SMP=y
33CONFIG_NR_CPUS=64
34# CONFIG_EXPERT is not set
35# end of General setup
36
37#
38# Security
39#
40CONFIG_SECURITY=y
41CONFIG_SECURITY_SELINUX=y
42# end of Security
43`;
44
45// ============================================================================
46// DESERIALIZATION TESTS
47// ============================================================================
48
49Deno.test("deserialize simple config", () => {
50 const config = KernelConfigDeserializer.deserialize(simpleConfig);
51 assertEquals(Object.keys(config.flatConfig).length, 5);
52});
53
54Deno.test("deserialize with sections", () => {
55 const config = KernelConfigDeserializer.deserialize(complexConfig, {
56 parseSections: true,
57 });
58 assert(config.sections.length > 0);
59});
60
61Deno.test("parse boolean values", () => {
62 const config = KernelConfigDeserializer.deserialize(simpleConfig);
63 assertEquals(config.flatConfig.CONFIG_64BIT, "y");
64 assertEquals(config.flatConfig.CONFIG_SMP, "y");
65});
66
67Deno.test("parse numeric values", () => {
68 const config = KernelConfigDeserializer.deserialize(simpleConfig);
69 assertEquals(config.flatConfig.CONFIG_NR_CPUS, 64);
70});
71
72Deno.test("parse string values", () => {
73 const config = KernelConfigDeserializer.deserialize(simpleConfig);
74 assertEquals(config.flatConfig.CONFIG_LOCALVERSION, "-test");
75});
76
77Deno.test("parse disabled options", () => {
78 const config = KernelConfigDeserializer.deserialize(simpleConfig);
79 assertEquals(config.flatConfig.CONFIG_DEBUG, undefined);
80});
81
82Deno.test("parse hex values", () => {
83 const hexConfig = "CONFIG_PHYSICAL_START=0x1000000";
84 const config = KernelConfigDeserializer.deserialize(hexConfig);
85 assertEquals(config.flatConfig.CONFIG_PHYSICAL_START, 0x1000000);
86});
87
88Deno.test("deserialize from JSON", () => {
89 const json = '{"sections":[],"flatConfig":{"CONFIG_SMP":"y"}}';
90 const config = KernelConfigDeserializer.fromJSON(json);
91 assertEquals(config.flatConfig.CONFIG_SMP, "y");
92});
93
94Deno.test("deserialize from object", () => {
95 const obj = { CONFIG_SMP: true, CONFIG_DEBUG: false };
96 const config = KernelConfigDeserializer.fromObject(obj);
97 assertEquals(config.flatConfig.CONFIG_SMP, "y");
98 assertEquals(config.flatConfig.CONFIG_DEBUG, undefined);
99});
100
101Deno.test("handle empty config", () => {
102 const config = KernelConfigDeserializer.deserialize("");
103 assertEquals(Object.keys(config.flatConfig).length, 0);
104});
105
106// ============================================================================
107// SERIALIZATION TESTS
108// ============================================================================
109
110Deno.test("serialize to config format", () => {
111 const config = KernelConfigDeserializer.deserialize(simpleConfig);
112 const serialized = KernelConfigSerializer.toConfig(config);
113 assert(serialized.includes("CONFIG_64BIT=y"));
114});
115
116Deno.test("serialize with custom options", () => {
117 const config = KernelConfigDeserializer.deserialize(simpleConfig);
118 const opts: SerializeOptions = { addHeader: false };
119 const serialized = KernelConfigSerializer.toConfig(config, opts);
120 assert(!serialized.startsWith("#"));
121});
122
123Deno.test("serialize sorted keys", () => {
124 const config = KernelConfigDeserializer.deserialize(simpleConfig);
125 const opts: SerializeOptions = { sortKeys: true };
126 const serialized = KernelConfigSerializer.toConfig(config, opts);
127 const lines = serialized.split("\n").filter((l) => l.startsWith("CONFIG_"));
128
129 // Check if sorted
130 for (let i = 1; i < lines.length; i++) {
131 assert(lines[i - 1] <= lines[i]);
132 }
133});
134
135Deno.test("serialize to JSON", () => {
136 const config = KernelConfigDeserializer.deserialize(simpleConfig);
137 const json = KernelConfigSerializer.toJSON(config);
138 const parsed = JSON.parse(json);
139 assertEquals(parsed.flatConfig.CONFIG_SMP, "y");
140});
141
142Deno.test("serialize to object", () => {
143 const config = KernelConfigDeserializer.deserialize(simpleConfig);
144 const obj = KernelConfigSerializer.toObject(config);
145 assertEquals(obj.CONFIG_64BIT, "y");
146});
147
148Deno.test("serialize to object with boolean style", () => {
149 const config = KernelConfigDeserializer.deserialize(simpleConfig);
150 const obj = KernelConfigSerializer.toObject(config, true);
151 assertEquals(obj.CONFIG_64BIT, true);
152 assertEquals(obj.CONFIG_DEBUG, false);
153});
154
155Deno.test("serialize to YAML", () => {
156 const config = KernelConfigDeserializer.deserialize(simpleConfig);
157 const yaml = KernelConfigSerializer.toYAML(config);
158 assert(yaml.includes("config:"));
159 assert(yaml.includes('CONFIG_SMP: "y"'));
160});
161
162Deno.test("serialize to Makefile", () => {
163 const config = KernelConfigDeserializer.deserialize(simpleConfig);
164 const makefile = KernelConfigSerializer.toMakefile(config);
165 assert(makefile.includes("SMP := y"));
166});
167
168Deno.test("serialize enabled only", () => {
169 const config = KernelConfigDeserializer.deserialize(simpleConfig);
170 const enabled = KernelConfigSerializer.toEnabledOnly(config);
171 assert(enabled.includes("CONFIG_SMP=y"));
172 assert(!enabled.includes("CONFIG_DEBUG"));
173});
174
175Deno.test("serialize to shell script", () => {
176 const config = KernelConfigDeserializer.deserialize(simpleConfig);
177 const shell = KernelConfigSerializer.toShellScript(config);
178 assert(shell.includes("#!/bin/bash"));
179 assert(shell.includes("export CONFIG_SMP=y"));
180});
181
182// ============================================================================
183// ROUND-TRIP TESTS
184// ============================================================================
185
186Deno.test("round-trip simple config", () => {
187 const original = KernelConfigDeserializer.deserialize(simpleConfig);
188 const serialized = KernelConfigSerializer.toConfig(original);
189 const roundtrip = KernelConfigDeserializer.deserialize(serialized);
190 assertEquals(
191 JSON.stringify(original.flatConfig),
192 JSON.stringify(roundtrip.flatConfig)
193 );
194});
195
196Deno.test("round-trip complex config", () => {
197 const original = KernelConfigDeserializer.deserialize(complexConfig);
198 const serialized = KernelConfigSerializer.toConfig(original);
199 const roundtrip = KernelConfigDeserializer.deserialize(serialized);
200 assertEquals(
201 Object.keys(original.flatConfig).length,
202 Object.keys(roundtrip.flatConfig).length
203 );
204});
205
206Deno.test("round-trip via JSON", () => {
207 const original = KernelConfigDeserializer.deserialize(simpleConfig);
208 const json = KernelConfigSerializer.toJSON(original);
209 const roundtrip = KernelConfigDeserializer.fromJSON(json);
210 assertEquals(
211 JSON.stringify(original.flatConfig),
212 JSON.stringify(roundtrip.flatConfig)
213 );
214});
215
216Deno.test("round-trip via object", () => {
217 const original = KernelConfigDeserializer.fromObject({
218 CONFIG_SMP: true,
219 CONFIG_DEBUG: false,
220 });
221 const obj = KernelConfigSerializer.toObject(original, true);
222 const roundtrip = KernelConfigDeserializer.fromObject(obj);
223 assertEquals(original.flatConfig.CONFIG_SMP, roundtrip.flatConfig.CONFIG_SMP);
224});
225
226// ============================================================================
227// DIFF TESTS
228// ============================================================================
229
230Deno.test("detect added options", () => {
231 const old = KernelConfigDeserializer.fromObject({ CONFIG_SMP: true });
232 const new_ = KernelConfigDeserializer.fromObject({
233 CONFIG_SMP: true,
234 CONFIG_DEBUG: true,
235 });
236 const diff = KernelConfigSerializer.toDiff(old, new_);
237 assert(diff.includes("+ CONFIG_DEBUG=y"));
238});
239
240Deno.test("detect removed options", () => {
241 const old = KernelConfigDeserializer.fromObject({
242 CONFIG_SMP: true,
243 CONFIG_DEBUG: true,
244 });
245 const new_ = KernelConfigDeserializer.fromObject({ CONFIG_SMP: true });
246 const diff = KernelConfigSerializer.toDiff(old, new_);
247 assert(diff.includes("- CONFIG_DEBUG=y"));
248});
249
250Deno.test("detect changed options", () => {
251 const old = KernelConfigDeserializer.fromObject({ CONFIG_NR_CPUS: 64 });
252 const new_ = KernelConfigDeserializer.fromObject({ CONFIG_NR_CPUS: 128 });
253 const diff = KernelConfigSerializer.toDiff(old, new_);
254 assert(diff.includes("- CONFIG_NR_CPUS=64"));
255 assert(diff.includes("+ CONFIG_NR_CPUS=128"));
256});
257
258// ============================================================================
259// EDGE CASES
260// ============================================================================
261
262Deno.test("handle quoted strings with spaces", () => {
263 const config = KernelConfigDeserializer.deserialize(
264 'CONFIG_TEXT="hello world"'
265 );
266 assertEquals(config.flatConfig.CONFIG_TEXT, "hello world");
267});
268
269Deno.test("handle quoted strings with special chars", () => {
270 const config = KernelConfigDeserializer.deserialize(
271 'CONFIG_TEXT="test#value"'
272 );
273 assertEquals(config.flatConfig.CONFIG_TEXT, "test#value");
274});
275
276Deno.test("handle empty string value", () => {
277 const config = KernelConfigDeserializer.deserialize('CONFIG_TEXT=""');
278 assertEquals(config.flatConfig.CONFIG_TEXT, "");
279});
280
281Deno.test("handle zero value", () => {
282 const config = KernelConfigDeserializer.deserialize("CONFIG_NUM=0");
283 assertEquals(config.flatConfig.CONFIG_NUM, 0);
284});
285
286Deno.test("handle negative numbers", () => {
287 const config = KernelConfigDeserializer.deserialize("CONFIG_NUM=-1");
288 assertEquals(config.flatConfig.CONFIG_NUM, -1);
289});
290
291Deno.test("handle module value (m)", () => {
292 const config = KernelConfigDeserializer.deserialize("CONFIG_MODULE=m");
293 assertEquals(config.flatConfig.CONFIG_MODULE, "m");
294});
295
296Deno.test("preserve section hierarchy", () => {
297 const config = KernelConfigDeserializer.deserialize(complexConfig);
298 const serialized = KernelConfigSerializer.toConfig(config, {
299 preserveSections: true,
300 });
301 assert(serialized.includes("# General setup"));
302 assert(serialized.includes("# Security"));
303});
304
305Deno.test("flatten sections when disabled", () => {
306 const config = KernelConfigDeserializer.deserialize(complexConfig);
307 const serialized = KernelConfigSerializer.toConfig(config, {
308 preserveSections: false,
309 });
310 assert(!serialized.includes("# end of"));
311});
312
313Deno.test("handle malformed input gracefully", () => {
314 const config = KernelConfigDeserializer.deserialize("INVALID LINE\n###\n", {
315 strict: false,
316 });
317 assertExists(config);
318});
319
320// ============================================================================
321// PARSER UTILITY TESTS
322// ============================================================================
323
324Deno.test("getValue returns correct value", () => {
325 const config = KernelConfigDeserializer.deserialize(simpleConfig);
326 const value = KernelConfigParser.getValue(config, "CONFIG_SMP");
327 assertEquals(value, "y");
328});
329
330Deno.test("getValue returns null for non-existent key", () => {
331 const config = KernelConfigDeserializer.deserialize(simpleConfig);
332 const value = KernelConfigParser.getValue(config, "CONFIG_NONEXISTENT");
333 assertEquals(value, undefined);
334});
335
336Deno.test("isEnabled detects enabled options", () => {
337 const config = KernelConfigDeserializer.deserialize(simpleConfig);
338 assert(KernelConfigParser.isEnabled(config, "CONFIG_SMP"));
339});
340
341Deno.test("isEnabled detects disabled options", () => {
342 const config = KernelConfigDeserializer.deserialize(simpleConfig);
343 assert(!KernelConfigParser.isEnabled(config, "CONFIG_DEBUG"));
344});
345
346Deno.test("extract processor config", () => {
347 const config = KernelConfigDeserializer.deserialize(simpleConfig);
348 const processor = KernelConfigParser.extractProcessorConfig(config);
349 assertEquals(processor.SMP, true);
350 assertEquals(processor.NR_CPUS, 64);
351});
352
353Deno.test("extract security config", () => {
354 const config = KernelConfigDeserializer.deserialize(complexConfig);
355 const security = KernelConfigParser.extractSecurityConfig(config);
356 assertEquals(security.SECURITY, true);
357 assertEquals(security.SECURITY_SELINUX, true);
358});
359
360// ============================================================================
361// TOML RELATED TESTS
362// ============================================================================
363
364Deno.test("toTOML includes buildInfo and config section", () => {
365 const original = KernelConfigDeserializer.fromObject({
366 CONFIG_SMP: true,
367 CONFIG_NR_CPUS: 8,
368 });
369
370 const configWithBuild = {
371 ...original,
372 buildInfo: { compiler: "gcc 13.3.0" },
373 };
374
375 const tomlStr = KernelConfigSerializer.toTOML(configWithBuild as any);
376 // should contain top-level tables for buildInfo and config
377 assert(tomlStr.includes("buildInfo"));
378 assert(tomlStr.includes("config"));
379 assert(tomlStr.includes("gcc 13.3.0"));
380});
381
382Deno.test("fromTOML parses flat TOML keys", () => {
383 const tomlStr = `CONFIG_SMP = "y"\nCONFIG_NR_CPUS = 32`;
384 const parsed = KernelConfigDeserializer.fromTOML(tomlStr);
385 assertEquals(parsed.flatConfig.CONFIG_SMP, "y");
386 assertEquals(parsed.flatConfig.CONFIG_NR_CPUS, 32);
387});
388
389Deno.test("fromTOML throws on invalid TOML", () => {
390 const invalid = "this is not = valid toml =";
391 assertThrows(() => {
392 KernelConfigDeserializer.fromTOML(invalid);
393 });
394});