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.
 
 
 
 
 

259 lines
6.3 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_PREFIX "asm/"
  11. #define ASM_OUTFILE ASM_PREFIX ".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_asm_test(const char *src_file, const char *ref_file)
  94. {
  95. char *cmd_prefix = "../crater --assemble " ASM_PREFIX;
  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/<src_file> asm/.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_PREFIX) + strlen(ref_file) + 1));
  108. stpcpy(stpcpy(ref_path, ASM_PREFIX), ref_file);
  109. bool diff = diff_files(ref_path, ASM_OUTFILE);
  110. free(ref_path);
  111. return diff;
  112. }
  113. /* --------------------------- Main test runners --------------------------- */
  114. /*
  115. Run tests for the Z80 CPU.
  116. */
  117. static bool test_cpu()
  118. {
  119. // TODO
  120. return true;
  121. }
  122. /*
  123. Run tests for the VDP.
  124. */
  125. static bool test_vdp()
  126. {
  127. // TODO
  128. return true;
  129. }
  130. /*
  131. Run tests for the SN76489 PSG.
  132. */
  133. static bool test_psg()
  134. {
  135. // TODO
  136. return true;
  137. }
  138. /*
  139. Run tests for the assembler.
  140. */
  141. static bool test_asm()
  142. {
  143. FILE *fp = fopen(ASM_PREFIX "manifest", "r");
  144. if (!fp) {
  145. ERROR_ERRNO("couldn't open manifest file")
  146. return false;
  147. }
  148. char *line = NULL, *split;
  149. size_t cap = 0, lineno = 0;
  150. ssize_t len;
  151. while ((len = getline(&line, &cap, fp)) > 0) {
  152. lineno++;
  153. line[--len] = '\0';
  154. if (!len)
  155. continue;
  156. // TODO: validate chars
  157. split = strchr(line, ' ');
  158. if (!split || strchr(split + 1, ' ')) {
  159. READY_STDOUT()
  160. ERROR("bad format in manifest file on line %zu", lineno)
  161. return false;
  162. }
  163. *(split++) = '\0';
  164. if (!run_asm_test(line, split)) {
  165. fprintf(stderr, "test: %s -> %s\n", line, split);
  166. return false;
  167. }
  168. PASS_TEST()
  169. }
  170. unlink(ASM_OUTFILE);
  171. free(line);
  172. return true;
  173. }
  174. /*
  175. Run tests for the disassembler.
  176. */
  177. static bool test_dis()
  178. {
  179. // TODO
  180. return true;
  181. }
  182. /*
  183. Run integration tests (i.e., multiple components working together).
  184. */
  185. static bool test_integrate()
  186. {
  187. // TODO
  188. return true;
  189. }
  190. /*
  191. Main function.
  192. */
  193. int main(int argc, char *argv[])
  194. {
  195. if (argc != 2)
  196. FATAL("a single component name is required")
  197. const char *component = argv[1], *name;
  198. bool (*func)();
  199. if (!strcmp(component, "cpu")) {
  200. name = "Z80 CPU";
  201. func = test_cpu;
  202. } else if (!strcmp(component, "vdp")) {
  203. name = "VDP";
  204. func = test_vdp;
  205. } else if (!strcmp(component, "psg")) {
  206. name = "SN76489 PSG";
  207. func = test_psg;
  208. } else if (!strcmp(component, "asm")) {
  209. name = "assembler";
  210. func = test_asm;
  211. } else if (!strcmp(component, "dis")) {
  212. name = "disassembler";
  213. func = test_dis;
  214. } else if (!strcmp(component, "integrate")) {
  215. name = "integration";
  216. func = test_integrate;
  217. } else {
  218. FATAL("unknown component: %s", component)
  219. }
  220. printf("crater: running %s tests\n", name);
  221. atexit(finalize);
  222. return func() ? EXIT_SUCCESS : EXIT_FAILURE;
  223. }