Simple script and config (type-safe) for building custom Linux kernels for Firecracker MicroVMs
at main 394 lines 14 kB view raw
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});