An emulator, assembler, and disassembler for the Sega Game Gear
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

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