def do(self, src, expected_output, lang="c3"): # Construct binary file from snippet: startercode = io.StringIO(self.startercode) base_filename = make_filename(self.id()) bsp_c3 = io.StringIO(self.bsp_c3_src) build( base_filename, src, bsp_c3, startercode, self.march, self.opt_level, io.StringIO(self.arch_mmap), lang=lang, elf_format="elf", ) base_filename = make_filename(self.id()) elf_filename = base_filename + ".elf" if has_qemu(): output = qemu([ "qemu-system-riscv32", "-nographic", "-M", "sifive_u", "-bios", elf_filename, ]) self.assertEqual(expected_output, output)
def do(self, src, expected_output, lang="c3"): # Construct binary file from snippet: bsp_c3 = relpath("..", "examples", "realview-pb-a8", "arch.c3") startercode = io.StringIO(self.startercode) base_filename = make_filename(self.id()) obj = build( base_filename, src, bsp_c3, startercode, self.march, self.opt_level, io.StringIO(self.arch_mmap), lang=lang, bin_format="bin", ) sample_filename = base_filename + ".bin" # Run bin file in emulator: if has_qemu(): res = qemu([ "qemu-system-arm", "--machine", "realview-pb-a8", "-m", "16M", "-nographic", "-kernel", sample_filename, ]) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'or1k', 'bsp.c3') crt0 = relpath('..', 'examples', 'or1k', 'crt0.asm') mmap = relpath('..', 'examples', 'or1k', 'layout.mmp') build(base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format='bin', code_image='flash') binfile = base_filename + '.bin' # Create a uboot application file: with open(binfile, 'rb') as f: bindata = f.read() img_filename = base_filename + '.img' with open(img_filename, 'wb') as f: uboot_image.write_uboot_image( f, bindata, arch=uboot_image.Architecture.OPENRISC) if has_qemu(): output = qemu([ 'qemu-system-or1k', '-nographic', '-M', 'or1k-sim', '-m', '16', '-kernel', img_filename ]) self.assertEqual(expected_output, output)
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) bsp_c3 = relpath("..", "examples", "xtensa", "bsp.c3") crt0 = relpath("..", "examples", "xtensa", "glue.asm") mmap = relpath("..", "examples", "xtensa", "layout.mmp") build( base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format="elf", code_image="flash", ) elf_filename = base_filename + ".elf" if has_qemu(): output = qemu([ "qemu-system-xtensa", "-nographic", "-M", "lx60", "-m", "16", "-kernel", elf_filename, ]) self.assertEqual(expected_output, output)
def test_numpy(self): source_file = io.StringIO(""" module main; function int find_first(int *a, int b, int size, int stride) { var int i = 0; var int ptr = 0; for ( i=0; i<size; i+=1 ) { if ( *(a + ptr) == b ) { return i; } ptr += stride; } return 0xff; } """) html_filename = make_filename(self.id()) + '.html' with open(html_filename, 'w') as f: with HtmlReportGenerator(f) as reporter: m = load_code_as_module(source_file, reporter=reporter) import numpy as np a = np.array([12, 7, 3, 5, 42, 8, 3, 5, 8, 1, 4, 6, 2], dtype=int) addr = ctypes.cast(a.ctypes.data, ctypes.POINTER(ctypes.c_int)) # Cross fingers pos = m.find_first(addr, 42, len(a), a.itemsize) self.assertEqual(4, pos) pos = m.find_first(addr, 200, len(a), a.itemsize) self.assertEqual(0xff, pos)
def test_jit_example(self): """ Test loading of C code from jit example """ source = io.StringIO(""" int x(int* a, int* b, int count) { int sum = 0; int i; for (i=0; i < count; i++) sum += a[i] * b[i]; return sum; } """) arch = get_current_arch() html_filename = make_filename(self.id()) + '.html' with open(html_filename, 'w') as f, HtmlReportGenerator(f) as reporter: obj = cc(source, arch, debug=True, reporter=reporter) m = load_obj(obj) # print(m.x.argtypes) T = ctypes.c_int * 6 a = T() # TODO: integers in ctypes are 32 bit, they are 64 bit in ppci? a[:] = 1, 0, 2, 0, 3, 0 # ap = ctypes.cast(a, ctypes.POINTER(ctypes.c_long)) b = T() b[:] = 5, 0, 4, 0, 9, 0 # bp = ctypes.cast(b, ctypes.POINTER(ctypes.c_long)) y = m.x(a, b, 3) self.assertEqual(40, y)
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) bsp_c3 = relpath("..", "examples", "microblaze", "bsp.c3") crt0 = relpath("..", "examples", "microblaze", "crt0.asm") mmap = relpath("..", "examples", "microblaze", "layout.mmp") build( base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format="bin", code_image="flash", ) bin_filename = base_filename + ".bin" if has_qemu(): output = qemu([ "qemu-system-microblaze", "-nographic", "-kernel", bin_filename, ]) self.assertEqual(expected_output, output)
def test_jit_example(self): """ Test loading of C code from jit example """ source = io.StringIO(""" int mega_complex_stuff(int* a, int* b, int count) { int sum = 0; int i; for (i=0; i < count; i++) sum += a[i] * b[i]; return sum; } """) arch = get_current_arch() html_filename = make_filename(self.id()) + '.html' with open(html_filename, 'w') as f, HtmlReportGenerator(f) as reporter: obj = cc(source, arch, debug=True, reporter=reporter) m = load_obj(obj) # print(m.x.argtypes) count = 6 T = ctypes.c_int * count a = T() a[:] = 1, 0, 2, 0, 3, 0 b = T() b[:] = 5, 0, 4, 0, 9, 0 y = m.mega_complex_stuff(a, b, count) self.assertEqual(40, y)
def do(self, src, expected_output, lang="c3"): # Compile: bsp_c3_src = """ module bsp; public function void putc(byte c); """ bsp_c3 = io.StringIO(bsp_c3_src) march = get_current_arch() base_filename = make_filename(self.id()) report_filename = base_filename + ".html" with html_reporter(report_filename) as reporter: obj = partial_build(src, lang, bsp_c3, 0, march, reporter) actual_output = [] def bsp_putc(c: int) -> None: # print('bsp_putc:', chr(c)) actual_output.append(chr(c)) # Dynamically load: imports = { "bsp_putc": bsp_putc, } mod = load_obj(obj, imports=imports) # print(dir(mod)) # Invoke! if hasattr(mod, "main"): mod.main() else: mod.main_main() # Check output: actual_output = "".join(actual_output) self.assertEqual(expected_output, actual_output)
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) bsp_c3 = relpath("..", "examples", "msp430", "bsp.c3") startercode = io.StringIO(self.startercode) obj = build( base_filename, src, bsp_c3, startercode, "msp430", self.opt_level, io.StringIO(self.arch_mmap), lang=lang, ) flash = obj.get_image("flash") ivect = obj.get_image("vector16") rom = merge_memories(flash, ivect, "rom") rom_data = rom.data assert len(rom_data) % 2 == 0 with open(base_filename + ".bin", "wb") as f: f.write(rom_data) mem_file = base_filename + ".mem" with open(mem_file, "w") as f: for i in range(len(rom_data) // 2): w = rom_data[2 * i:2 * i + 2] print("%02x%02x" % (w[1], w[0]), file=f) if has_iverilog() and do_iverilog(): res = run_msp430(mem_file) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'msp430', 'bsp.c3') startercode = io.StringIO(self.startercode) obj = build(base_filename, src, bsp_c3, startercode, "msp430", self.opt_level, io.StringIO(self.arch_mmap), lang=lang) flash = obj.get_image('flash') ivect = obj.get_image('vector16') rom = merge_memories(flash, ivect, 'rom') rom_data = rom.data assert len(rom_data) % 2 == 0 with open(base_filename + '.bin', 'wb') as f: f.write(rom_data) mem_file = base_filename + '.mem' with open(mem_file, 'w') as f: for i in range(len(rom_data) // 2): w = rom_data[2 * i:2 * i + 2] print('%02x%02x' % (w[1], w[0]), file=f) if has_iverilog() and do_iverilog(): res = run_msp430(mem_file) self.assertEqual(expected_output, res)
def test_numpy_floated(self): # TODO: add '10.2' instead of '10'. Somehow, adding 10.2 does not work source_file = io.StringIO(""" module main; function void mlt(double* a, double *b, int size, int stride) { var int i = 0; var int ptr = 0; for ( i=0; i<size; i+=1 ) { *(a + ptr) = *(a + ptr) + *(b + ptr) * 2 + 10; ptr += stride; } } """) html_filename = make_filename(self.id()) + '.html' with open(html_filename, 'w') as f, HtmlReportGenerator(f) as r: m = load_code_as_module(source_file, reporter=r) import numpy as np a = np.array([12, 7, 3, 5, 42, 100], dtype=float) b = np.array([82, 2, 5, 8, 13, 600], dtype=float) c = np.array([186, 21, 23, 31, 78, 1310], dtype=float) ap = ctypes.cast(a.ctypes.data, ctypes.POINTER(ctypes.c_double)) bp = ctypes.cast(b.ctypes.data, ctypes.POINTER(ctypes.c_double)) m.mlt(ap, bp, len(a), a.itemsize) # print(a) # print(c) self.assertTrue(np.allclose(c, a))
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) sample_filename = base_filename + '.py' list_filename = base_filename + '.html' bsp = io.StringIO(""" module bsp; public function void putc(byte c); """) march = 'arm' with HtmlReportGenerator(open(list_filename, 'w')) as reporter: if lang == 'c3': ir_modules = [ c3_to_ir([ relpath('..', 'librt', 'io.c3'), bsp, io.StringIO(src) ], [], march, reporter=reporter) ] elif lang == 'bf': ir_modules = [bf_to_ir(src, march)] elif lang == 'c': coptions = COptions() include_path1 = relpath('..', 'librt', 'libc') lib = relpath('..', 'librt', 'libc', 'lib.c') coptions.add_include_path(include_path1) with open(lib, 'r') as f: mod1 = c_to_ir(f, march, coptions=coptions, reporter=reporter) mod2 = c_to_ir(io.StringIO(src), march, coptions=coptions, reporter=reporter) ir_modules = [mod1, mod2] else: # pragma: no cover raise NotImplementedError( 'Language {} not implemented'.format(lang)) # Test roundtrip of ir_modules for ir_module in ir_modules: serialization_roundtrip(ir_module) optimize(ir_module, level=self.opt_level, reporter=reporter) with open(sample_filename, 'w') as f: ir_to_python(ir_modules, f, reporter=reporter) # Add glue: print('', file=f) print('def bsp_putc(c):', file=f) print(' print(chr(c), end="")', file=f) print('main_main()', file=f) res = run_python(sample_filename) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'stm8', 'bsp.c3') crt0 = relpath('..', 'examples', 'stm8', 'start.asm') mmap = relpath('..', 'examples', 'avr', 'avr.mmap') build(base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format='hex', code_image='flash')
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) bsp_c3 = relpath("..", "examples", "stm8", "bsp.c3") crt0 = relpath("..", "examples", "stm8", "start.asm") mmap = relpath("..", "examples", "avr", "avr.mmap") build( base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format="hex", code_image="flash", )
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) sample_filename = base_filename + ".py" list_filename = base_filename + ".html" bsp_c3 = io.StringIO(""" module bsp; public function void putc(byte c); """) march = "arm" with html_reporter(list_filename) as reporter: ir_modules = build_sample_to_ir(src, lang, bsp_c3, march, reporter) # Test roundtrip of ir_modules for ir_module in ir_modules: verify_module(ir_module) serialization_roundtrip(ir_module) api.optimize(ir_module, level=self.opt_level, reporter=reporter) with open(sample_filename, "w") as f: api.ir_to_python(ir_modules, f, reporter=reporter) # Expose all functions as external symbols: for ir_module in ir_modules: for routine in ir_module.functions: print('_irpy_externals["{0}"] = {0}'.format( routine.name), file=f) # Add glue: print("", file=f) print("def bsp_putc(c):", file=f) print(' print(chr(c), end="")', file=f) print("", file=f) # print('_irpy_externals["printf"] = printf', file=f) print('_irpy_externals["bsp_putc"] = bsp_putc', file=f) print("", file=f) print("main_main()", file=f) print("", file=f) res = run_python(sample_filename) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) bsp_c3 = relpath("..", "examples", "or1k", "bsp.c3") crt0 = relpath("..", "examples", "or1k", "crt0.asm") mmap = relpath("..", "examples", "or1k", "layout.mmp") build( base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format="bin", code_image="flash", ) binfile = base_filename + ".bin" # Create a uboot application file: with open(binfile, "rb") as f: bindata = f.read() img_filename = base_filename + ".img" with open(img_filename, "wb") as f: uboot_image.write_uboot_image( f, bindata, arch=uboot_image.Architecture.OPENRISC) qemu_cmd = [ "qemu-system-or1k", "-nographic", "-M", "or1k-sim", "-m", "16", "-kernel", img_filename, ] create_qemu_launch_script(base_filename + ".sh", qemu_cmd) if has_qemu(): output = qemu(qemu_cmd) self.assertEqual(expected_output, output)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'avr', 'bsp.c3') crt0 = relpath('..', 'examples', 'avr', 'glue.asm') mmap = relpath('..', 'examples', 'avr', 'avr.mmap') build(base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format='hex', code_image='flash') hexfile = base_filename + '.hex' print(hexfile) if has_avr_emulator() and do_iverilog(): res = run_avr(hexfile) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang="c3"): # Construct binary file from snippet: startercode = io.StringIO(self.startercode) base_filename = make_filename(self.id()) bsp_c3 = io.StringIO(self.bsp_c3_src) obj = build( base_filename, src, bsp_c3, startercode, self.march, self.opt_level, io.StringIO(self.arch_mmap), lang=lang, bin_format="bin", elf_format="elf", code_image="flash", ) flash = obj.get_image("flash") data = obj.get_image("ram") rom = merge_memories(flash, data, "rom") rom_data = rom.data filewordsize = 0x8000 datawordlen = len(rom_data) // 4 mem_file = base_filename + ".mem" with open(mem_file, "w") as f: for i in range(filewordsize): if i < datawordlen: w = rom_data[4 * i:4 * i + 4] print("%02x%02x%02x%02x" % (w[3], w[2], w[1], w[0]), file=f) else: print("00000000", file=f) f.close() if has_iverilog() and do_iverilog(): res = run_picorv32(mem_file) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'xtensa', 'bsp.c3') crt0 = relpath('..', 'examples', 'xtensa', 'glue.asm') mmap = relpath('..', 'examples', 'xtensa', 'layout.mmp') build(base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format='elf', code_image='flash') elf_filename = base_filename + '.elf' if has_qemu(): output = qemu([ 'qemu-system-xtensa', '-nographic', '-M', 'lx60', '-m', '16', '-kernel', elf_filename ]) self.assertEqual(expected_output, output)
def do(self, src, expected_output, lang="c3"): # Construct binary file from snippet: startercode = io.StringIO(self.startercode) base_filename = make_filename(self.id()) bsp_c3 = io.StringIO(self.bsp_c3_src) obj = build(base_filename, src, bsp_c3, startercode, self.march, self.opt_level, io.StringIO(self.arch_mmap), lang=lang, bin_format='bin', elf_format='elf', code_image='flash') flash = obj.get_image('flash') data = obj.get_image('ram') rom = merge_memories(flash, data, 'rom') rom_data = rom.data filewordsize = 0x8000 datawordlen = len(rom_data) // 4 mem_file = base_filename + '.mem' with open(mem_file, 'w') as f: for i in range(filewordsize): if (i < datawordlen): w = rom_data[4 * i:4 * i + 4] print('%02x%02x%02x%02x' % (w[3], w[2], w[1], w[0]), file=f) else: print('00000000', file=f) f.close() if has_iverilog() and do_iverilog(): res = run_picorv32(mem_file) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) bsp_c3 = relpath("..", "examples", "avr", "bsp.c3") crt0 = relpath("..", "examples", "avr", "glue.asm") mmap = relpath("..", "examples", "avr", "avr.mmap") build( base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format="hex", code_image="flash", ) hexfile = base_filename + ".hex" print(hexfile) if has_avr_emulator() and do_iverilog(): res = run_avr(hexfile) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'microblaze', 'bsp.c3') crt0 = relpath('..', 'examples', 'microblaze', 'crt0.asm') mmap = relpath('..', 'examples', 'microblaze', 'layout.mmp') build(base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format='bin', code_image='flash') bin_filename = base_filename + '.bin' if has_qemu(): output = qemu([ 'qemu-system-microblaze', '-nographic', '-kernel', bin_filename ]) self.assertEqual(expected_output, output)
def do(self, src, expected_output, lang="c3"): base_filename = make_filename(self.id()) list_filename = base_filename + ".html" bsp_c3 = io.StringIO(""" module bsp; public function void putc(byte c); """) march = "arm" # TODO: this must be wasm! with html_reporter(list_filename) as reporter: ir_modules = build_sample_to_ir(src, lang, bsp_c3, march, reporter) for ir_module in ir_modules: api.optimize(ir_module, level=self.opt_level, reporter=reporter) wasm_module = ir_to_wasm(ir_link(ir_modules), reporter=reporter) # Output wasm file: wasm_filename = base_filename + ".wasm" with open(wasm_filename, "wb") as f: wasm_module.to_file(f) # Dat was 'm: wasm = wasm_module.to_bytes() wasm_text = str(list(wasm)) wasm_data = "var wasm_data = new Uint8Array(" + wasm_text + ");" # Output javascript file: js = NODE_JS_TEMPLATE.replace("JS_PLACEHOLDER", wasm_data) js_filename = base_filename + ".js" with open(js_filename, "w") as f: f.write(js) # run node.js and compare output: res = run_nodejs(js_filename) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang="c3"): # Construct binary file from snippet: bsp_c3 = relpath('..', 'examples', 'realview-pb-a8', 'arch.c3') startercode = io.StringIO(self.startercode) base_filename = make_filename(self.id()) obj = build(base_filename, src, bsp_c3, startercode, self.march, self.opt_level, io.StringIO(self.arch_mmap), lang=lang, bin_format='bin') sample_filename = base_filename + '.bin' # Run bin file in emulator: if has_qemu(): res = qemu([ 'qemu-system-arm', '--machine', 'realview-pb-a8', '-m', '16M', '-nographic', '-kernel', sample_filename ]) self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): bsp_c3 = io.StringIO(self.bsp_c3_src) startercode = io.StringIO(self.startercode) base_filename = make_filename(self.id()) obj = build(base_filename, src, bsp_c3, startercode, self.march, self.opt_level, io.StringIO(self.arch_mmap), lang=lang, bin_format='elf') exe = base_filename + '.elf' if has_linux(): if hasattr(subprocess, 'TimeoutExpired'): res = subprocess.check_output(exe, timeout=10) else: res = subprocess.check_output(exe) res = res.decode('ascii') self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) bsp_c3 = relpath('..', 'examples', 'xtensa', 'bsp.c3') crt0 = relpath('..', 'examples', 'xtensa', 'glue.asm') mmap = relpath('..', 'examples', 'xtensa', 'layout.mmp') build(base_filename, src, bsp_c3, crt0, self.march, self.opt_level, mmap, lang=lang, bin_format='bin', code_image='flash') binfile = base_filename + '.bin' img_filename = base_filename + '.img' self.make_image(binfile, img_filename) if has_qemu(): output = qemu([ 'qemu-system-xtensa', '-nographic', '-M', 'lx60', '-m', '16', '-drive', 'if=pflash,format=raw,file={}'.format(img_filename) ]) self.assertEqual(expected_output, output)
def do(self, src, expected_output, lang="c3"): bsp_c3 = io.StringIO(BSP_C3_SRC) base_filename = make_filename(self.id()) build( base_filename, src, bsp_c3, io.StringIO(STARTERCODE), self.march, self.opt_level, io.StringIO(ARCH_MMAP), lang=lang, bin_format="elf", ) exe = base_filename + ".elf" if has_linux(): if hasattr(subprocess, "TimeoutExpired"): res = subprocess.check_output(exe, timeout=10) else: res = subprocess.check_output(exe) res = res.decode("ascii") self.assertEqual(expected_output, res)
def do(self, src, expected_output, lang='c3'): base_filename = make_filename(self.id()) list_filename = base_filename + '.html' bsp = io.StringIO(""" module bsp; public function void putc(byte c); """) march = 'arm' # TODO: this must be wasm! with HtmlReportGenerator(open(list_filename, 'w')) as reporter: if lang == 'c3': ir_modules = [ c3_to_ir([ bsp, relpath('..', 'librt', 'io.c3'), io.StringIO(src) ], [], march, reporter=reporter) ] elif lang == 'bf': ir_modules = [bf_to_ir(src, march)] elif lang == 'c': coptions = COptions() include_path1 = relpath('..', 'librt', 'libc') lib = relpath('..', 'librt', 'libc', 'lib.c') coptions.add_include_path(include_path1) with open(lib, 'r') as f: mod1 = c_to_ir(f, march, coptions=coptions, reporter=reporter) mod2 = c_to_ir(io.StringIO(src), march, coptions=coptions, reporter=reporter) ir_modules = [mod1, mod2] else: # pragma: no cover raise NotImplementedError( 'Language {} not implemented'.format(lang)) for ir_module in ir_modules: optimize(ir_module, level=self.opt_level, reporter=reporter) wasm_module = ir_to_wasm(ir_link(ir_modules), reporter=reporter) # Output wasm file: wasm_filename = base_filename + '.wasm' with open(wasm_filename, 'wb') as f: wasm_module.to_file(f) # Dat was 'm: wasm = wasm_module.to_bytes() wasm_text = str(list(wasm)) wasm_data = 'var wasm_data = new Uint8Array(' + wasm_text + ');' # Output javascript file: js = NODE_JS_TEMPLATE.replace('JS_PLACEHOLDER', wasm_data) js_filename = base_filename + '.js' with open(js_filename, 'w') as f: f.write(js) # run node.js and compare output: res = run_nodejs(js_filename) self.assertEqual(expected_output, res)