From c74b1a85e28e527b0b26dd67072f6ffc48ab2158 Mon Sep 17 00:00:00 2001 From: hernanxho Date: Wed, 27 May 2026 23:18:57 -0500 Subject: [PATCH 1/4] test(regression): add regression tests to document known behaviors and bugs --- test/regression.test.ts | 288 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 test/regression.test.ts diff --git a/test/regression.test.ts b/test/regression.test.ts new file mode 100644 index 0000000..4981c8a --- /dev/null +++ b/test/regression.test.ts @@ -0,0 +1,288 @@ +// test/regression.test.ts +// +// PROPÓSITO: Documentar bugs conocidos y comportamientos específicos del sistema +// para que nadie los rompa accidentalmente en el futuro. +// +// DIFERENCIA con otros tests: +// - integration.test.ts / e2e.test.ts → flujo completo en casos normales +// - boundary.test.ts → valores extremos de operandos +// - bit.utils.test.ts → primitivas de bits en aislamiento +// - ← ESTE TEST → fija comportamientos concretos que ya se saben +// correctos (o incorrectos) para evitar regresiones +// +// Cada prueba documenta POR QUÉ existe y qué bug/comportamiento protege. + +import { describe, it, expect } from 'vitest'; +import { parseAssembly } from '../src/services/assembly-parser.service/assembly-parser.service'; +import { parseInstructions } from '../src/services/instruction.parser.service'; +import { encodeInstruction, decodeInstruction } from '../src/services/handler.registry.service'; +import { formatDecodedInstruction } from '../src/utils/format.utils'; + +// ─── Helper ────────────────────────────────────────────────────────────────── + +const fullPipeline = (program: string) => { + const { instructions, errors } = parseAssembly(program); + if (errors.length > 0) return { errors, hexes: [], decoded: [] }; + const decoded = parseInstructions(instructions); + const hexes = decoded.map(i => encodeInstruction(i, 'r6')); + return { errors, hexes, decoded }; +}; + +// ─── 1. ORDEN DE OPERANDOS ──────────────────────────────────────────────────── +// Bug documentado en parser.test.ts: el orden de los registros en la salida +// del decoder fue fuente de confusión en el equipo (se esperaba rd,rs,rt +// pero se recibía en otro orden en alguna prueba). + +describe('Regresión – orden de operandos', () => { + + it('add: el decoder devuelve [rd, rs, rt] en ese orden', () => { + // add $t0, $t1, $t2 → rd=$t0, rs=$t1, rt=$t2 + const back = decodeInstruction('0x012A4020', 'r6'); + expect(back.mnemonic).toBe('add'); + expect(back.operands[0]).toEqual({ kind: 'register', name: 't0' }); // rd + expect(back.operands[1]).toEqual({ kind: 'register', name: 't1' }); // rs + expect(back.operands[2]).toEqual({ kind: 'register', name: 't2' }); // rt + }); + + it('add: formatDecodedInstruction produce "add $t0 $t1 $t2" en ese orden', () => { + const back = decodeInstruction('0x012A4020', 'r6'); + expect(formatDecodedInstruction(back)).toBe('add $t0 $t1 $t2'); + }); + + it('sub: el decoder devuelve [rd, rs, rt] en ese orden', () => { + const { hexes } = fullPipeline('sub $t0, $t1, $t2'); + const back = decodeInstruction(hexes[0]!, 'r6'); + expect(back.operands[0]).toEqual({ kind: 'register', name: 't0' }); + expect(back.operands[1]).toEqual({ kind: 'register', name: 't1' }); + expect(back.operands[2]).toEqual({ kind: 'register', name: 't2' }); + }); + + it('addiu: el parser produce [rt, rs, imm] — rt primero, rs segundo', () => { + // En MIPS I-type: opcode | rs | rt | imm + // Pero el parser expone la instrucción como "addiu rt, rs, imm" + const { instructions } = parseAssembly('addiu $t0, $zero, 42'); + expect(instructions[0]).toBe('addiu $t0 $zero 42'); + }); + + it('lw: el decoder devuelve [rt, memory(base,offset)] — rt primero', () => { + const back = decodeInstruction('0x8FB00004', 'r6'); + expect(back.mnemonic).toBe('lw'); + expect(back.operands[0]).toEqual({ kind: 'register', name: 's0' }); + expect(back.operands[1]).toEqual({ kind: 'memory', base: 'sp', offset: 4 }); + }); + + it('sll: el decoder devuelve [rd, rt, shamt] — sin rs', () => { + const { hexes } = fullPipeline('sll $t0, $t1, 4'); + const back = decodeInstruction(hexes[0]!, 'r6'); + expect(back.mnemonic).toBe('sll'); + expect(back.operands).toHaveLength(3); + expect(back.operands[0]).toEqual({ kind: 'register', name: 't0' }); // rd + expect(back.operands[1]).toEqual({ kind: 'register', name: 't1' }); // rt + expect(back.operands[2]).toEqual({ kind: 'immediate', value: 4 }); // shamt + }); + +}); + +// ─── 2. BUG CONOCIDO: DECODER DE -32768 ────────────────────────────────────── +// Documentado por boundary.test.ts: al decodificar addiu $t0, $zero, -32768 +// el decoder devuelve 32768 en lugar de -32768 porque el i-type handler +// usa parseInt(imm16, 2) sin aplicar bitsToSignedNum. +// Este bloque FIJA el comportamiento actual para detectar si se corrige +// o si se introduce una regresión diferente. + +describe('Regresión – bug decoder inmediato -32768', () => { + + it('CONOCIDO: decodificar 0x24088000 devuelve 32768 (no -32768)', () => { + // Este test documenta el bug. Si empieza a fallar, significa + // que alguien corrigió el decoder — en ese caso actualizar este + // test para esperar -32768 y moverlo al bloque de correcciones. + const back = decodeInstruction('0x24088000', 'r6'); + expect(back.operands[2]).toEqual({ kind: 'immediate', value: 32768 }); + // ESPERADO CORRECTO SERÍA: { kind: 'immediate', value: -32768 } + }); + + it('el encoder SÍ produce el hex correcto para -32768', () => { + // El bug está solo en el decoder, no en el encoder + const { hexes, errors } = fullPipeline('addiu $t0, $zero, -32768'); + expect(errors).toHaveLength(0); + expect(hexes[0]).toMatch(/8000$/); // los 16 bits bajos = 0x8000 + }); + + it('valores negativos menores a -1 también se ven afectados en decode', () => { + // -2 en 16 bits = 0xFFFE — este SÍ decodifica bien porque + // parseInt('1111111111111110', 2) = 65534, pero el handler + // no aplica signo. Documentamos que valores > 32767 son "positivos" incorrectos. + const { hexes } = fullPipeline('addiu $t0, $zero, -1'); + const back = decodeInstruction(hexes[0]!, 'r6'); + // -1 en 16 bits = 0xFFFF = 65535 sin signo + expect(back.operands[2]).toEqual({ kind: 'immediate', value: 65535 }); + }); + +}); + +// ─── 3. COMPORTAMIENTO DE LABELS Y OFFSETS ─────────────────────────────────── +// Protege el cálculo de offsets para branches y targets para jumps, +// que fue discutido durante el desarrollo del proyecto. + +describe('Regresión – labels y cálculo de offsets', () => { + + it('branch offset hacia adelante: offset = destino - (pc_branch + 1)', () => { + const program = ` + beq $t0, $zero, end + addiu $t1, $zero, 1 + end: addiu $t2, $zero, 2 + `; + const { instructions } = parseAssembly(program); + // beq está en índice 0, end está en índice 2 + // offset = 2 - (0 + 1) = 1 + expect(instructions[0]).toBe('beq $t0 $zero 1'); + }); + + it('branch offset hacia atrás: offset es negativo', () => { + const program = ` + addiu $t0, $zero, 10 + loop: addiu $t0, $t0, -1 + bne $t0, $zero, loop + `; + const { instructions } = parseAssembly(program); + // bne está en índice 2, loop está en índice 1 + // offset = 1 - (2 + 1) = -2 + expect(instructions[2]).toBe('bne $t0 $zero -2'); + }); + + it('branch a la siguiente instrucción: offset = 0', () => { + const program = ` + beq $t0, $zero, next + next: addiu $t1, $zero, 1 + `; + const { instructions } = parseAssembly(program); + expect(instructions[0]).toBe('beq $t0 $zero 0'); + }); + + it('jal: el target se calcula como dirección absoluta / 4', () => { + const program = ` + jal fib + addiu $t0, $zero, 0 + fib: addiu $t1, $zero, 1 + `; + const { instructions, errors } = parseAssembly(program); + expect(errors).toHaveLength(0); + // fib está en índice 2 → dirección = BASE + 2*4 = 0x00400000 + 8 = 0x00400008 + // target = 0x00400008 >> 2 = 0x00100002 = 1048578 + expect(instructions[0]).toBe('jal 1048578'); + }); + + it('etiqueta duplicada produce error con mensaje específico', () => { + const program = ` + loop: addiu $t0, $zero, 1 + loop: addiu $t1, $zero, 2 + `; + const { errors } = parseAssembly(program); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0]).toContain('Etiqueta duplicada'); + expect(errors[0]).toContain('loop'); + }); + + it('etiqueta no definida produce error con mensaje específico', () => { + const program = `beq $t0, $zero, fantasma`; + const { errors } = parseAssembly(program); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0]).toContain('etiqueta no definida'); + expect(errors[0]).toContain('fantasma'); + }); + +}); + +// ─── 4. INSTRUCCIONES ESPECIALES ───────────────────────────────────────────── +// syscall, break, jr y jalr tienen comportamiento especial que fue +// implementado en el stress test — protegemos esos hexes aquí. + +describe('Regresión – instrucciones especiales', () => { + + it('syscall encodea a 0x0000000C', () => { + const { hexes } = fullPipeline('syscall'); + expect(hexes[0]).toBe('0x0000000C'); + }); + + it('break encodea a 0x0000000D', () => { + const { hexes } = fullPipeline('break'); + expect(hexes[0]).toBe('0x0000000D'); + }); + + it('jr $ra encodea a 0x03E00008', () => { + const { hexes } = fullPipeline('jr $ra'); + expect(hexes[0]).toBe('0x03E00008'); + }); + + it('jr $zero encodea a 0x00000008', () => { + const { hexes } = fullPipeline('jr $zero'); + expect(hexes[0]).toBe('0x00000008'); + }); + + it('syscall round-trip: decode devuelve mnemónico syscall', () => { + const back = decodeInstruction('0x0000000C', 'r6'); + expect(back.mnemonic).toBe('syscall'); + expect(back.operands).toHaveLength(0); + }); + + it('lui: codifica correctamente (rs = $zero implícito)', () => { + const { hexes, errors } = fullPipeline('lui $t0, 1'); + expect(errors).toHaveLength(0); + // opcode=001111, rs=00000, rt=01000, imm=0000000000000001 + expect(hexes[0]).toBe('0x3C080001'); + }); + + it('lui round-trip preserva mnemónico y operandos', () => { + const { hexes } = fullPipeline('lui $t0, 1'); + const back = decodeInstruction(hexes[0]!, 'r6'); + expect(back.mnemonic).toBe('lui'); + expect(back.operands[0]).toEqual({ kind: 'register', name: 't0' }); + expect(back.operands[1]).toEqual({ kind: 'immediate', value: 1 }); + }); + +}); + +// ─── 5. FORMATO DE SALIDA ───────────────────────────────────────────────────── +// El formato exacto de los mensajes de error y de las instrucciones formateadas +// importa porque otros módulos pueden depender de él. + +describe('Regresión – formato de salida', () => { + + it('formatDecodedInstruction usa $ en los registros', () => { + const back = decodeInstruction('0x012A4020', 'r6'); + const formatted = formatDecodedInstruction(back); + expect(formatted).toContain('$t0'); + expect(formatted).toContain('$t1'); + expect(formatted).toContain('$t2'); + }); + + it('formatDecodedInstruction produce el formato "mnemónico $rd $rs $rt"', () => { + const back = decodeInstruction('0x012A4020', 'r6'); + expect(formatDecodedInstruction(back)).toBe('add $t0 $t1 $t2'); + }); + + it('el hex producido siempre tiene prefijo 0x en mayúsculas', () => { + const cases = [ + 'add $t0, $t1, $t2', + 'addiu $t0, $zero, 42', + 'lw $s0, 4($sp)', + 'syscall', + ]; + for (const asm of cases) { + const { hexes } = fullPipeline(asm); + expect(hexes[0]).toMatch(/^0x[0-9A-F]{8}$/); + } + }); + + it('los mensajes de error contienen el número de línea', () => { + const { errors } = parseAssembly('fakeop $t0, $t1, $t2'); + expect(errors[0]).toContain('Línea 1'); + }); + + it('el parser normaliza los registros a minúsculas sin $', () => { + const { instructions } = parseAssembly('add $T0, $T1, $T2'); + // El parser debe normalizar a minúsculas + expect(instructions[0]?.toLowerCase()).toContain('t0'); + }); + +}); From a7a35483cc4416adf96d2a637bdb1877d1d5ef30 Mon Sep 17 00:00:00 2001 From: hernanxho Date: Wed, 27 May 2026 23:25:58 -0500 Subject: [PATCH 2/4] test(coverage): add coverage tests for all supported MIPS R6 instructions --- test/coverage.test.ts | 409 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 test/coverage.test.ts diff --git a/test/coverage.test.ts b/test/coverage.test.ts new file mode 100644 index 0000000..ce39b58 --- /dev/null +++ b/test/coverage.test.ts @@ -0,0 +1,409 @@ +// test/coverage.test.ts +// +// PROPÓSITO: Verificar sistemáticamente que CADA instrucción soportada +// por el proyecto puede ser parseada, encodada y decodificada sin errores. +// +// DIFERENCIA con otros tests: +// - integration.test.ts / e2e.test.ts → flujo completo en casos representativos +// - stress.test.ts → un programa complejo (Fibonacci) +// - registry.test.ts → encode/decode de instrucciones individuales +// - ← ESTE TEST → pasa por TODAS las instrucciones del JSON una por una, +// asegurando que ninguna quede sin probar +// +// Si se agrega una instrucción nueva al JSON y el handler no la soporta, +// este test lo detecta antes de que llegue a producción. + +import { describe, it, expect } from 'vitest'; +import { parseAssembly } from '../src/services/assembly-parser.service/assembly-parser.service'; +import { parseInstructions } from '../src/services/instruction.parser.service'; +import { encodeInstruction, decodeInstruction } from '../src/services/handler.registry.service'; + +// ─── Helper ────────────────────────────────────────────────────────────────── + +const encodeAsm = (asm: string) => { + const { instructions, errors } = parseAssembly(asm); + if (errors.length > 0) return { hex: null, errors }; + const decoded = parseInstructions(instructions); + const hex = encodeInstruction(decoded[0]!, 'r6'); + return { hex, errors: [] }; +}; + +const roundTrip = (asm: string) => { + const { hex, errors } = encodeAsm(asm); + if (!hex) return { ok: false, errors }; + const back = decodeInstruction(hex, 'r6'); + return { ok: true, mnemonic: back.mnemonic, operands: back.operands, errors: [] }; +}; + +// ─── 1. INSTRUCCIONES R-TYPE ────────────────────────────────────────────────── + +describe('Coverage – R-type: aritmética entera', () => { + + it('add $t0, $t1, $t2 → encode sin errores', () => { + const { errors, hex } = encodeAsm('add $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + expect(hex).toBe('0x012A4020'); + }); + + it('addu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('addu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('sub $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('sub $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('subu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('subu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('mul $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('mul $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('muh $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('muh $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('mulu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('mulu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('muhu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('muhu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('div $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('div $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('mod $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('mod $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('divu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('divu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('modu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('modu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + +}); + +describe('Coverage – R-type: lógica', () => { + + it('and $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('and $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('or $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('or $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('xor $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('xor $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('nor $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('nor $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('slt $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('slt $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('sltu $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('sltu $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('seleqz $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('seleqz $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('selnez $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('selnez $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + +}); + +describe('Coverage – R-type: shifts', () => { + + it('sll $t0, $t1, 4 → encode sin errores', () => { + const { errors } = encodeAsm('sll $t0, $t1, 4'); + expect(errors).toHaveLength(0); + }); + + it('srl $t0, $t1, 4 → encode sin errores', () => { + const { errors } = encodeAsm('srl $t0, $t1, 4'); + expect(errors).toHaveLength(0); + }); + + it('sra $t0, $t1, 4 → encode sin errores', () => { + const { errors } = encodeAsm('sra $t0, $t1, 4'); + expect(errors).toHaveLength(0); + }); + + it('sllv $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('sllv $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('srlv $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('srlv $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + + it('srav $t0, $t1, $t2 → encode sin errores', () => { + const { errors } = encodeAsm('srav $t0, $t1, $t2'); + expect(errors).toHaveLength(0); + }); + +}); + +describe('Coverage – R-type: saltos y especiales', () => { + + it('jr $ra → encode sin errores', () => { + const { errors } = encodeAsm('jr $ra'); + expect(errors).toHaveLength(0); + }); + + it('jalr $ra, $t0 → encode sin errores', () => { + const { errors } = encodeAsm('jalr $ra, $t0'); + expect(errors).toHaveLength(0); + }); + + it('syscall → encode sin errores', () => { + const { errors } = encodeAsm('syscall'); + expect(errors).toHaveLength(0); + }); + + it('break → encode sin errores', () => { + const { errors } = encodeAsm('break'); + expect(errors).toHaveLength(0); + }); + +}); + +// ─── 2. INSTRUCCIONES I-TYPE ────────────────────────────────────────────────── + +describe('Coverage – I-type: aritmética/lógica inmediata', () => { + + it('addiu $t0, $zero, 42 → encode sin errores', () => { + const { errors } = encodeAsm('addiu $t0, $zero, 42'); + expect(errors).toHaveLength(0); + }); + + it('slti $t0, $t1, 10 → encode sin errores', () => { + const { errors } = encodeAsm('slti $t0, $t1, 10'); + expect(errors).toHaveLength(0); + }); + + it('sltiu $t0, $t1, 10 → encode sin errores', () => { + const { errors } = encodeAsm('sltiu $t0, $t1, 10'); + expect(errors).toHaveLength(0); + }); + + it('andi $t0, $t1, 0xFF → encode sin errores', () => { + const { errors } = encodeAsm('andi $t0, $t1, 0xFF'); + expect(errors).toHaveLength(0); + }); + + it('ori $t0, $t1, 0xFF → encode sin errores', () => { + const { errors } = encodeAsm('ori $t0, $t1, 0xFF'); + expect(errors).toHaveLength(0); + }); + + it('xori $t0, $t1, 0xFF → encode sin errores', () => { + const { errors } = encodeAsm('xori $t0, $t1, 0xFF'); + expect(errors).toHaveLength(0); + }); + + it('lui $t0, 1 → encode sin errores', () => { + const { errors } = encodeAsm('lui $t0, 1'); + expect(errors).toHaveLength(0); + }); + +}); + +describe('Coverage – I-type: memoria (loads)', () => { + + it('lb $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('lb $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + + it('lh $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('lh $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + + it('lw $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('lw $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + + it('lbu $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('lbu $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + + it('lhu $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('lhu $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + +}); + +describe('Coverage – I-type: memoria (stores)', () => { + + it('sb $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('sb $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + + it('sh $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('sh $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + + it('sw $t0, 0($sp) → encode sin errores', () => { + const { errors } = encodeAsm('sw $t0, 0($sp)'); + expect(errors).toHaveLength(0); + }); + +}); + +describe('Coverage – I-type: branches clásicos', () => { + + it('beq $t0, $t1, label → encode sin errores', () => { + const { errors } = parseAssembly('beq $t0, $t1, end\nend: addiu $zero, $zero, 0'); + expect(errors).toHaveLength(0); + }); + + it('bne $t0, $t1, label → encode sin errores', () => { + const { errors } = parseAssembly('bne $t0, $t1, end\nend: addiu $zero, $zero, 0'); + expect(errors).toHaveLength(0); + }); + + it('blez $t0, label → encode sin errores', () => { + const { errors } = parseAssembly('blez $t0, end\nend: addiu $zero, $zero, 0'); + expect(errors).toHaveLength(0); + }); + + it('bgtz $t0, label → encode sin errores', () => { + const { errors } = parseAssembly('bgtz $t0, end\nend: addiu $zero, $zero, 0'); + expect(errors).toHaveLength(0); + }); + + it('bltz $t0, label → encode sin errores', () => { + const { errors } = parseAssembly('bltz $t0, end\nend: addiu $zero, $zero, 0'); + expect(errors).toHaveLength(0); + }); + + it('bgez $t0, label → encode sin errores', () => { + const { errors } = parseAssembly('bgez $t0, end\nend: addiu $zero, $zero, 0'); + expect(errors).toHaveLength(0); + }); + +}); + +// ─── 3. INSTRUCCIONES J-TYPE ────────────────────────────────────────────────── + +describe('Coverage – J-type', () => { + + it('j label → encode sin errores', () => { + const { errors } = parseAssembly('j fin\naddiu $t0, $zero, 1\nfin: addiu $t1, $zero, 2'); + expect(errors).toHaveLength(0); + }); + + it('jal label → encode sin errores', () => { + const { errors } = parseAssembly('jal fin\naddiu $t0, $zero, 1\nfin: addiu $t1, $zero, 2'); + expect(errors).toHaveLength(0); + }); + +}); + +// ─── 4. ROUND-TRIP POR TIPO ─────────────────────────────────────────────────── +// Verifica que para cada categoría, el mnemónico sobrevive encode → decode + +describe('Coverage – round-trip mnemónico por instrucción', () => { + + const rTypeInstructions = [ + ['add', 'add $t0, $t1, $t2'], + ['addu', 'addu $t0, $t1, $t2'], + ['sub', 'sub $t0, $t1, $t2'], + ['subu', 'subu $t0, $t1, $t2'], + ['and', 'and $t0, $t1, $t2'], + ['or', 'or $t0, $t1, $t2'], + ['xor', 'xor $t0, $t1, $t2'], + ['nor', 'nor $t0, $t1, $t2'], + ['slt', 'slt $t0, $t1, $t2'], + ['sltu', 'sltu $t0, $t1, $t2'], + ['mul', 'mul $t0, $t1, $t2'], + ['div', 'div $t0, $t1, $t2'], + ['mod', 'mod $t0, $t1, $t2'], + ['sll', 'sll $t0, $t1, 4'], + ['srl', 'srl $t0, $t1, 4'], + ['sra', 'sra $t0, $t1, 4'], + ['syscall','syscall'], + ['break', 'break'], + ['jr', 'jr $ra'], + ] as const; + + for (const [mnemonic, asm] of rTypeInstructions) { + it(`round-trip ${mnemonic}: decode(encode(x)).mnemonic === '${mnemonic}'`, () => { + const result = roundTrip(asm); + expect(result.errors).toHaveLength(0); + expect(result.ok).toBe(true); + expect(result.mnemonic).toBe(mnemonic); + }); + } + + const iTypeInstructions = [ + ['addiu', 'addiu $t0, $zero, 42'], + ['slti', 'slti $t0, $t1, 10'], + ['sltiu', 'sltiu $t0, $t1, 10'], + ['andi', 'andi $t0, $t1, 10'], + ['ori', 'ori $t0, $t1, 10'], + ['xori', 'xori $t0, $t1, 10'], + ['lui', 'lui $t0, 1'], + ['lw', 'lw $t0, 4($sp)'], + ['sw', 'sw $t0, 4($sp)'], + ['lb', 'lb $t0, 0($sp)'], + ['lh', 'lh $t0, 0($sp)'], + ['lbu', 'lbu $t0, 0($sp)'], + ['lhu', 'lhu $t0, 0($sp)'], + ['sb', 'sb $t0, 0($sp)'], + ['sh', 'sh $t0, 0($sp)'], + ] as const; + + for (const [mnemonic, asm] of iTypeInstructions) { + it(`round-trip ${mnemonic}: decode(encode(x)).mnemonic === '${mnemonic}'`, () => { + const result = roundTrip(asm); + expect(result.errors).toHaveLength(0); + expect(result.ok).toBe(true); + expect(result.mnemonic).toBe(mnemonic); + }); + } + +}); From 1056f7b6030642880747bbf9090b393e29a107dd Mon Sep 17 00:00:00 2001 From: Sebastian Brito Date: Thu, 28 May 2026 22:54:35 -0500 Subject: [PATCH 3/4] readme --- README.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..99264f6 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# MIPS Instruction Decoder & Encoder + +Este proyecto es una herramienta y librería en **TypeScript** diseñada para realizar el flujo completo de **análisis (parsing), codificación (encoding), decodificación (decoding) y formateo** de instrucciones de la arquitectura **MIPS**. + +Soporta conjuntos de instrucciones tanto **MIPS I (Legacy)** como **MIPS R6**. + +--- + +## Requisitos Previos + +Asegúrate de tener instalados los siguientes componentes antes de iniciar: +* [Node.js](https://nodejs.org/) (Versión 18 o superior recomendada) +* [pnpm](https://pnpm.io/) (Gestor de paquetes utilizado en este proyecto) + +--- + +## Instalación de Dependencias + +Para instalar todas las dependencias necesarias del proyecto, abre tu terminal en la carpeta raíz del proyecto y ejecuta: + +```bash +pnpm install +``` + +--- + +## Cómo Ejecutar las Pruebas (Tests) + +Este proyecto utiliza **Vitest** como framework de pruebas. A continuación tienes los comandos para los diferentes modos de ejecución: + +### 1. Ejecutar todas las pruebas una vez (CI/CD / Verificación rápida) +```bash +pnpm test run +``` + +### 2. Ejecutar pruebas en modo observador (Watch Mode) +Las pruebas se ejecutan automáticamente cada vez que editas o guardas un archivo: +```bash +pnpm test +``` + +### 3. Ejecutar un archivo de prueba específico +Si estás trabajando en una parte específica y quieres probar solo ese archivo (por ejemplo, `e2e.test.ts`): +```bash +pnpm test test/e2e.test.ts --run +``` + +### 4. Generar reporte de cobertura de código (Coverage) +Para analizar qué partes del código están cubiertas por las pruebas unitarias: +```bash +pnpm exec vitest run --coverage +``` + +--- + +## Cómo Ejecutar y Compilar el Código + +### Opción A: Ejecución directa en desarrollo (Sin compilar manualmente) +Puedes correr directamente cualquier script de TypeScript usando `tsx` de manera rápida: + +```bash +npx tsx src/main.ts +``` + +### Opción B: Proceso de compilación y ejecución estándar (Producción) + +1. **Compilar el proyecto** a JavaScript nativo: + ```bash + pnpm exec tsc + ``` + *(Esto generará los archivos resultantes en el directorio `dist/`)* + +2. **Ejecutar el archivo compilado**: + ```bash + node dist/main.js + ``` + +--- + +## 📁 Estructura Principal del Proyecto + +El proyecto está organizado de la siguiente manera: + +* 📂 **`src/`** — Código fuente principal. + * 📂 `src/constants/` — Constantes de encondings para MIPS R6 y Legacy. + * 📂 `src/services/` — Parsers de código ensamblador y gestores de encoding/decoding. + * 📂 `src/utils/` — Funciones de ayuda (manipulación de bits, registros, formato de operandos). + * 📄 `src/main.ts` — Punto de entrada de la aplicación. +* 📂 **`test/`** — Conjunto completo de pruebas unitarias, de integración, rendimiento, estrés y E2E. +* 📄 `tsconfig.json` — Configuración del compilador TypeScript. +* 📄 `vitest.config.ts` — Configuración del framework Vitest. From 45fdbaa02de5ce3a684340524efafc6804878204 Mon Sep 17 00:00:00 2001 From: Sebastian Brito Date: Wed, 3 Jun 2026 20:50:57 -0500 Subject: [PATCH 4/4] testing.md --- README.md | 68 ++++++++-------- TESTING.md | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 34 deletions(-) create mode 100644 TESTING.md diff --git a/README.md b/README.md index 99264f6..899b3f7 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ # MIPS Instruction Decoder & Encoder -Este proyecto es una herramienta y librería en **TypeScript** diseñada para realizar el flujo completo de **análisis (parsing), codificación (encoding), decodificación (decoding) y formateo** de instrucciones de la arquitectura **MIPS**. +This project is a tool and library written in **TypeScript** designed to perform the complete workflow of **parsing, encoding, decoding, and formatting** MIPS architecture instructions. -Soporta conjuntos de instrucciones tanto **MIPS I (Legacy)** como **MIPS R6**. +It supports both **MIPS I (Legacy)** and **MIPS R6** instruction sets. --- -## Requisitos Previos +## Prerequisites -Asegúrate de tener instalados los siguientes componentes antes de iniciar: -* [Node.js](https://nodejs.org/) (Versión 18 o superior recomendada) -* [pnpm](https://pnpm.io/) (Gestor de paquetes utilizado en este proyecto) +Before getting started, make sure you have the following installed: +* [Node.js](https://nodejs.org/) (Version 18 or higher recommended) +* [pnpm](https://pnpm.io/) (The package manager used in this project) --- -## Instalación de Dependencias +## Dependency Installation -Para instalar todas las dependencias necesarias del proyecto, abre tu terminal en la carpeta raíz del proyecto y ejecuta: +To install all the necessary project dependencies, open your terminal in the project's root folder and run: ```bash pnpm install @@ -24,68 +24,68 @@ pnpm install --- -## Cómo Ejecutar las Pruebas (Tests) +## How To Run The Tests -Este proyecto utiliza **Vitest** como framework de pruebas. A continuación tienes los comandos para los diferentes modos de ejecución: +This project uses **Vitest** as its test runner. Here are the commands for different execution modes: -### 1. Ejecutar todas las pruebas una vez (CI/CD / Verificación rápida) +### 1. Run all tests once (CI/CD / Quick Verification) ```bash pnpm test run ``` -### 2. Ejecutar pruebas en modo observador (Watch Mode) -Las pruebas se ejecutan automáticamente cada vez que editas o guardas un archivo: +### 2. Run tests in watch mode +Tests will run automatically every time you edit or save a file: ```bash pnpm test ``` -### 3. Ejecutar un archivo de prueba específico -Si estás trabajando en una parte específica y quieres probar solo ese archivo (por ejemplo, `e2e.test.ts`): +### 3. Run a specific test file +If you are working on a specific feature and want to test only that file (e.g., `e2e.test.ts`): ```bash pnpm test test/e2e.test.ts --run ``` -### 4. Generar reporte de cobertura de código (Coverage) -Para analizar qué partes del código están cubiertas por las pruebas unitarias: +### 4. Generate code coverage reports +To see how much of the code is covered by unit tests: ```bash pnpm exec vitest run --coverage ``` --- -## Cómo Ejecutar y Compilar el Código +## How To Run and Build The Code -### Opción A: Ejecución directa en desarrollo (Sin compilar manualmente) -Puedes correr directamente cualquier script de TypeScript usando `tsx` de manera rápida: +### Option A: Direct execution in development (No manual compilation required) +You can directly run any TypeScript file (such as the entry point `src/main.ts`) using `tsx`: ```bash npx tsx src/main.ts ``` -### Opción B: Proceso de compilación y ejecución estándar (Producción) +### Option B: Standard compilation and execution (Production) -1. **Compilar el proyecto** a JavaScript nativo: +1. **Compile the project** to native JavaScript: ```bash pnpm exec tsc ``` - *(Esto generará los archivos resultantes en el directorio `dist/`)* + *(This will generate the output files inside the `dist/` directory)* -2. **Ejecutar el archivo compilado**: +2. **Execute the compiled file**: ```bash node dist/main.js ``` --- -## 📁 Estructura Principal del Proyecto +## Main Project Structure -El proyecto está organizado de la siguiente manera: +The project is structured as follows: -* 📂 **`src/`** — Código fuente principal. - * 📂 `src/constants/` — Constantes de encondings para MIPS R6 y Legacy. - * 📂 `src/services/` — Parsers de código ensamblador y gestores de encoding/decoding. - * 📂 `src/utils/` — Funciones de ayuda (manipulación de bits, registros, formato de operandos). - * 📄 `src/main.ts` — Punto de entrada de la aplicación. -* 📂 **`test/`** — Conjunto completo de pruebas unitarias, de integración, rendimiento, estrés y E2E. -* 📄 `tsconfig.json` — Configuración del compilador TypeScript. -* 📄 `vitest.config.ts` — Configuración del framework Vitest. +* 📂 **`src/`** — Main source code. + * 📂 `src/constants/` — Encoding constant mappings for MIPS R6 and Legacy. + * 📂 `src/services/` — Assembly parsers, instruction parsers, and registry services. + * 📂 `src/utils/` — Helper functions (bit manipulation, registers, operand formatters). + * 📄 `src/main.ts` — Main entry point. +* 📂 **`test/`** — Test suites including unit, integration, performance, stress, and E2E tests. +* 📄 `tsconfig.json` — TypeScript compiler configuration. +* 📄 `vitest.config.ts` — Vitest configuration file. diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..6f6af09 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,222 @@ +# TESTING STRATEGY & SUITES + +This document describes the test environment, suite structure, and commands required to verify the correct operation of the **MIPS Instruction Decoder & Encoder**. + +The project uses **Vitest** as a next-generation test runner due to its speed, native TypeScript support, and ES Modules compatibility. + +--- + +## How To Run The Tests (Quick Commands) + +You can copy and paste these commands directly into your terminal: + +### Run all tests once (CI/CD / Quick Verification) +```bash +pnpm test run +``` +*Or with npm:* +```bash +npm run test -- --run +``` + +### Run tests in interactive watch mode +```bash +pnpm test +``` +*Or with npm:* +```bash +npm run test +``` + +### Run tests with a detailed code coverage report +```bash +pnpm exec vitest run --coverage +``` +*Or with npm:* +```bash +npx vitest run --coverage +``` + +--- + +## Test Suites Breakdown + +The project contains **12 specific test files** in the `test/` directory, covering everything from basic syntax parsing to complex regressions and performance benchmarks. + +--- + +### 1. INTEGRATION TESTS +* **File:** [test/integration.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/integration.test.ts) +* **Goal:** Validate the integration of the complete flow (`assembly code` ➔ `32-bit hexadecimal` ➔ `decoded instruction` ➔ `formatted text`). +* **Key Details:** + * Compares exact instruction encodings, such as `lw $s0, 4($sp)` (resulting in `0x8FB00004`). + * Executes complete programs including backward loops (branches with negative offset) and a **full recursive Fibonacci program** (30 instructions, validating offset calculations for labels, `jal`, and `jr` behavior). + +#### Command to run: +```bash +pnpm test test/integration.test.ts --run +``` + +--- + +### 2. END-TO-END TESTS (E2E) +* **File:** [test/e2e.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/e2e.test.ts) +* **Goal:** Prove that the full pipeline does not fail, executes instructions within optimal performance bounds, and catches syntax errors. +* **Key Details:** + * Performs full round-trip tests for R-type, I-type, jumps, branches, and memory instructions. + * Validates execution time limits for 100 consecutive pipeline runs. + * Verifies syntax exception handling: duplicate labels, invalid registers (e.g., `$xyz`), and out-of-bounds 16-bit immediates. + +#### Command to run: +```bash +pnpm test test/e2e.test.ts --run +``` + +--- + +### 3. BIT MANIPULATION UNIT TESTS (BIT UTILS) +* **File:** [test/bit.utils.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/bit.utils.test.ts) +* **Goal:** Verify the precision of binary string manipulation helper utilities for the encoder/decoder. +* **Key Details:** + * Tests conversions for `hexToBits` and `bitsToHex`. + * Conversions for signed integers using two's complement (`constToBits`) and back to numbers (`bitsToSignedNum` / `bitsToUnsignedNum`). + * Extracting exact bit ranges for opcodes, registers (`rs`, `rt`, `rd`), `shamt`, `funct`, and immediates. + +#### Command to run: +```bash +pnpm test test/bit.utils.test.ts --run +``` + +--- + +### 4. LIMIT AND FRONTIER TESTS (BOUNDARY TESTS) +* **File:** [test/boundary.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/boundary.test.ts) +* **Goal:** Analyze the pipeline's behavior when processing minimum, maximum, and boundary values. +* **Key Details:** + * 16-bit signed immediates: validates exact boundaries for `-32768` and `32767`. + * Verifies shift amount (`shamt`) bounds in the `[0, 31]` range. + * Validates behavior for special registers (like `$zero`) and hexadecimal immediates. + +#### Command to run: +```bash +pnpm test test/boundary.test.ts --run +``` + +--- + +### 5. CONSTANTS INTEGRITY TESTS +* **File:** [test/constants.integrity.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/constants.integrity.test.ts) +* **Goal:** Prevent opcode collisions, duplicate functions, or mismapped registers from breaking instruction encoding/decoding. +* **Key Details:** + * Validates that there are no duplicate names or bit mappings in the registers dictionary. + * Verifies uniqueness of the `opcode` + `funct` pairs in both Legacy and R6 encoding tables. + +#### Command to run: +```bash +pnpm test test/constants.integrity.test.ts --run +``` + +--- + +### 6. INSTRUCTION COVERAGE TESTS +* **File:** [test/coverage.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/coverage.test.ts) +* **Goal:** Ensure every supported MIPS instruction has at least one functional test case verifying a full round-trip. +* **Key Details:** + * Covers arithmetic (`add`, `sub`, `addu`), logical (`and`, `or`, `xor`), shift, branch, and memory (`lw`, `sw`) instructions. + +#### Command to run: +```bash +pnpm test test/coverage.test.ts --run +``` + +--- + +### 7. ASSEMBLY PARSER TESTS +* **File:** [test/parser.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/parser.test.ts) +* **Goal:** Ensure the syntactic formatter correctly interprets the textual structure of MIPS assembly code. +* **Key Details:** + * Handles comments (`#`), extra whitespaces, line breaks, and tabs. + * Extracts labels and computes relative jump offsets. + +#### Command to run: +```bash +pnpm test test/parser.test.ts --run +``` + +--- + +### 8. HANDLER REGISTRY TESTS +* **File:** [test/registry.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/registry.test.ts) +* **Goal:** Verify the dynamic registry mapping assembly instructions to their binary handlers. +* **Key Details:** + * Tests searching for MIPS instructions by object format. + * Tests reverse-lookup matching bits extracted from 32-bit hexadecimal strings. + +#### Command to run: +```bash +pnpm test test/registry.test.ts --run +``` + +--- + +### 9. REGRESSION TESTS +* **File:** [test/regression.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/regression.test.ts) +* **Goal:** Prevent previously fixed bugs from being accidentally reintroduced into the codebase. +* **Key Details:** + * Verifies the operand ordering fix for specific R-type instructions. + * Verifies the boundary immediate bug fix in the decoder (specifically for `-32768`). + * Ensures label relative offset calculation logic remains correct. + +#### Command to run: +```bash +pnpm test test/regression.test.ts --run +``` + +--- + +### 10. STRESS TESTS +* **File:** [test/stress.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/stress.test.ts) +* **Goal:** Validate robustness by running consecutive round-trip conversions for 100% of defined instructions. +* **Key Details:** + * Encodes and decodes every registered instruction in sequence to guarantee full stability. + +#### Command to run: +```bash +pnpm test test/stress.test.ts --run +``` + +--- + +### 11. PERFORMANCE & SCALABILITY TESTS +* **File:** [test/performance.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/performance.test.ts) +* **Goal:** Ensure the library remains fast and does not introduce bottlenecks as the volume of source instructions increases. +* **Key Details:** + * Benchmarks performance by compiling thousands of instructions per second. + * Validates that the average response time per instruction remains under fraction-of-a-millisecond thresholds. + +#### Command to run: +```bash +pnpm test test/performance.test.ts --run +``` + +--- + +### 12. DIAGNOSTIC TESTS +* **File:** [test/diagnostic.test.ts](file:///c:/Users/sebas/OneDrive/Escritorio/Arkad/Instruction_Decoder/test/diagnostic.test.ts) +* **Goal:** Print the loader configuration inside the encoder for visual auditing. +* **Key Details:** + * Outputs a structured console table (`console.table`) of all active opcodes, funct fields, shamt fields, and target instruction versions. + +#### Command to run: +```bash +pnpm test test/diagnostic.test.ts --run +``` + +--- + +## What It Means When These Tests Pass + +Green test status across the entire suite guarantees that: +1. **Binary consistency is preserved**: Every instruction translates to its standard MIPS hexadecimal counterpart bidirectionally. +2. **The assembly parser works**: Textual MIPS assembly is transformed into structured objects without losing operand details. +3. **The software is robust and fast**: The pipeline processes programs efficiently and returns helpful syntax error details to users instead of crashing.