An emulator, assembler, and disassembler for the Sega Game Gear
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

265 lines
6.4 KiB

  1. /* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. Released under the terms of the MIT License. See LICENSE for details. */
  3. #include <stdbool.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <unistd.h>
  8. #include "../src/logging.h"
  9. #include "../src/util.h"
  10. #define ASM_FULL "asm/full/"
  11. #define ASM_OUTFILE ASM_FULL ".output.gg"
  12. /* Helper macros for reporting test passings/failures */
  13. #define PASS_TEST() \
  14. do { \
  15. printf("."); \
  16. fflush(stdout); \
  17. passed_tests++; \
  18. pending_nl = true; \
  19. } while(0);
  20. #define FAIL_TEST(format, ...) \
  21. do { \
  22. printf("F\n"); \
  23. fprintf(stderr, "***** FAILURE *****\n" format "\n", __VA_ARGS__); \
  24. failed_tests++; \
  25. pending_nl = false; \
  26. } while(0);
  27. #define READY_STDOUT() \
  28. do { \
  29. if (pending_nl) { \
  30. printf("\n"); \
  31. pending_nl = false; \
  32. } \
  33. } while(0);
  34. static int passed_tests = 0, failed_tests = 0;
  35. static bool pending_nl = false;
  36. /*
  37. Prints out the test report. Called before exiting using atexit().
  38. */
  39. static void finalize() {
  40. READY_STDOUT()
  41. if (failed_tests)
  42. printf("fail (%d/%d)\n", passed_tests, passed_tests + failed_tests);
  43. else
  44. printf("pass (%d/%d)\n", passed_tests, passed_tests);
  45. }
  46. /*
  47. Compare two files. If they are identical, then return true. Otherwise,
  48. return false and print an error message showing the difference.
  49. */
  50. static bool diff_files(const char *expected_path, const char *actual_path)
  51. {
  52. bool same = false;
  53. FILE *expected = NULL, *actual = NULL;
  54. if (!(expected = fopen(expected_path, "rb"))) {
  55. FAIL_TEST("missing reference file: %s", expected_path)
  56. goto cleanup;
  57. }
  58. if (!(actual = fopen(actual_path, "rb"))) {
  59. FAIL_TEST("missing output file: %s", actual_path)
  60. goto cleanup;
  61. }
  62. size_t len = 0;
  63. int e, a;
  64. while ((e = fgetc(expected)) != EOF) {
  65. a = fgetc(actual);
  66. if (a == EOF) {
  67. FAIL_TEST("files differ: output file too short (index %zu)", len)
  68. goto cleanup;
  69. }
  70. if (e != a) {
  71. FAIL_TEST("files differ: 0x%02X != 0x%02X (expected vs. actual at "
  72. "index %zu)", e, a, len)
  73. goto cleanup;
  74. }
  75. len++;
  76. }
  77. if (fgetc(actual) != EOF) {
  78. FAIL_TEST("files differ: junk at end of output file (index %zu)", len)
  79. goto cleanup;
  80. }
  81. same = true;
  82. cleanup:
  83. if (expected)
  84. fclose(expected);
  85. if (actual)
  86. fclose(actual);
  87. return same;
  88. }
  89. /*
  90. Run a single ASM->ROM test, converting the given source file to a temporary
  91. output file, compared against the reference file.
  92. */
  93. static bool run_full_asm_test(const char *src_file, const char *ref_file)
  94. {
  95. char *cmd_prefix = "../crater --assemble " ASM_FULL;
  96. char *cmd = cr_malloc(sizeof(char) *
  97. (strlen(cmd_prefix) + strlen(ASM_OUTFILE) + strlen(src_file)) + 2);
  98. // Construct the command by concatenating:
  99. // ../crater --assemble asm/full/<src_file> asm/full/.output.gg
  100. stpcpy(stpcpy(stpcpy(cmd, cmd_prefix), src_file), " " ASM_OUTFILE);
  101. unlink(ASM_OUTFILE);
  102. system(cmd);
  103. free(cmd);
  104. // Construct the full reference file path in a temporary variable and diff
  105. // it with the output file:
  106. char *ref_path = malloc(sizeof(char) *
  107. (strlen(ASM_FULL) + strlen(ref_file) + 1));
  108. stpcpy(stpcpy(ref_path, ASM_FULL), ref_file);
  109. bool diff = diff_files(ref_path, ASM_OUTFILE);
  110. free(ref_path);
  111. return diff;
  112. }
  113. /*
  114. Run all "full"/"complete" ASM->ROM tests.
  115. */
  116. static bool run_full_asm_tests()
  117. {
  118. FILE *fp = fopen(ASM_FULL "manifest", "r");
  119. if (!fp) {
  120. ERROR_ERRNO("couldn't open manifest file")
  121. return false;
  122. }
  123. char *line = NULL, *split;
  124. size_t cap = 0, lineno = 0;
  125. ssize_t len;
  126. while ((len = getline(&line, &cap, fp)) > 0) {
  127. lineno++;
  128. line[--len] = '\0';
  129. if (!len)
  130. continue;
  131. // TODO: validate chars
  132. split = strchr(line, ' ');
  133. if (!split || strchr(split + 1, ' ')) {
  134. READY_STDOUT()
  135. ERROR("bad format in manifest file on line %zu", lineno)
  136. return false;
  137. }
  138. *(split++) = '\0';
  139. if (!run_full_asm_test(line, split)) {
  140. fprintf(stderr, "test: %s -> %s\n", line, split);
  141. return false;
  142. }
  143. PASS_TEST()
  144. }
  145. unlink(ASM_OUTFILE);
  146. free(line);
  147. return true;
  148. }
  149. /*
  150. Run tests for the Z80 CPU.
  151. */
  152. static bool test_cpu()
  153. {
  154. // TODO
  155. return true;
  156. }
  157. /*
  158. Run tests for the VDP.
  159. */
  160. static bool test_vdp()
  161. {
  162. // TODO
  163. return true;
  164. }
  165. /*
  166. Run tests for the SN76489 PSG.
  167. */
  168. static bool test_psg()
  169. {
  170. // TODO
  171. return true;
  172. }
  173. /*
  174. Run tests for the assembler.
  175. */
  176. static bool test_asm()
  177. {
  178. return run_full_asm_tests();
  179. }
  180. /*
  181. Run tests for the disassembler.
  182. */
  183. static bool test_dis()
  184. {
  185. // TODO
  186. return true;
  187. }
  188. /*
  189. Run integration tests (i.e., multiple components working together).
  190. */
  191. static bool test_integrate()
  192. {
  193. // TODO
  194. return true;
  195. }
  196. /*
  197. Main function.
  198. */
  199. int main(int argc, char *argv[])
  200. {
  201. if (argc != 2)
  202. FATAL("a single component name is required")
  203. const char *component = argv[1], *name;
  204. bool (*func)();
  205. if (!strcmp(component, "cpu")) {
  206. name = "Z80 CPU";
  207. func = test_cpu;
  208. } else if (!strcmp(component, "vdp")) {
  209. name = "VDP";
  210. func = test_vdp;
  211. } else if (!strcmp(component, "psg")) {
  212. name = "SN76489 PSG";
  213. func = test_psg;
  214. } else if (!strcmp(component, "asm")) {
  215. name = "assembler";
  216. func = test_asm;
  217. } else if (!strcmp(component, "dis")) {
  218. name = "disassembler";
  219. func = test_dis;
  220. } else if (!strcmp(component, "integrate")) {
  221. name = "integration";
  222. func = test_integrate;
  223. } else {
  224. FATAL("unknown component: %s", component)
  225. }
  226. printf("crater: running %s tests\n", name);
  227. atexit(finalize);
  228. return func() ? EXIT_SUCCESS : EXIT_FAILURE;
  229. }