An emulator, assembler, and disassembler for the Sega Game Gear
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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 (!(actual = fopen(actual_path, "rb"))) {
  55. FAIL_TEST("missing output file: %s", actual_path)
  56. goto cleanup;
  57. }
  58. if (!(expected = fopen(expected_path, "rb"))) {
  59. FAIL_TEST("missing reference file: %s", expected_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. Return whether the given character is valid within a filename.
  91. */
  92. static bool is_valid_filename_char(char c) {
  93. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
  94. (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';
  95. }
  96. /*
  97. Run a single ASM->ROM test, converting the given source file to a temporary
  98. output file, compared against the reference file.
  99. */
  100. static bool run_asm_test(const char *src_file, const char *ref_file)
  101. {
  102. char *cmd_prefix = "../crater --assemble " ASM_PREFIX;
  103. char *cmd = cr_malloc(sizeof(char) *
  104. (strlen(cmd_prefix) + strlen(ASM_OUTFILE) + strlen(src_file)) + 2);
  105. // Construct the command by concatenating:
  106. // ../crater --assemble asm/<src_file> asm/.output.gg
  107. stpcpy(stpcpy(stpcpy(cmd, cmd_prefix), src_file), " " ASM_OUTFILE);
  108. unlink(ASM_OUTFILE);
  109. system(cmd);
  110. free(cmd);
  111. // Construct the full reference file path in a temporary variable and diff
  112. // it with the output file:
  113. char *ref_path = malloc(sizeof(char) *
  114. (strlen(ASM_PREFIX) + strlen(ref_file) + 1));
  115. stpcpy(stpcpy(ref_path, ASM_PREFIX), ref_file);
  116. bool diff = diff_files(ref_path, ASM_OUTFILE);
  117. free(ref_path);
  118. return diff;
  119. }
  120. /* --------------------------- Main test runners --------------------------- */
  121. /*
  122. Run tests for the Z80 CPU.
  123. */
  124. static bool test_cpu()
  125. {
  126. // TODO
  127. return true;
  128. }
  129. /*
  130. Run tests for the VDP.
  131. */
  132. static bool test_vdp()
  133. {
  134. // TODO
  135. return true;
  136. }
  137. /*
  138. Run tests for the SN76489 PSG.
  139. */
  140. static bool test_psg()
  141. {
  142. // TODO
  143. return true;
  144. }
  145. /*
  146. Run tests for the assembler.
  147. */
  148. static bool test_asm()
  149. {
  150. FILE *fp = fopen(ASM_PREFIX "manifest", "r");
  151. if (!fp) {
  152. ERROR_ERRNO("couldn't open manifest file")
  153. return false;
  154. }
  155. char *line = NULL, *split, c;
  156. size_t cap = 0, lineno = 0, i;
  157. ssize_t len;
  158. while ((len = getline(&line, &cap, fp)) > 0) {
  159. lineno++;
  160. line[--len] = '\0';
  161. if (!len)
  162. continue;
  163. i = 0;
  164. while ((c = line[i++])) {
  165. if (!is_valid_filename_char(c) && c != ' ') {
  166. READY_STDOUT()
  167. ERROR("bad character in manifest file on line %zu", lineno)
  168. return false;
  169. }
  170. }
  171. split = strchr(line, ' ');
  172. if (!split || strchr(split + 1, ' ')) {
  173. READY_STDOUT()
  174. ERROR("bad format in manifest file on line %zu", lineno)
  175. return false;
  176. }
  177. *(split++) = '\0';
  178. if (!run_asm_test(line, split)) {
  179. fprintf(stderr, "test: %s -> %s\n", line, split);
  180. return false;
  181. }
  182. PASS_TEST()
  183. }
  184. unlink(ASM_OUTFILE);
  185. free(line);
  186. return true;
  187. }
  188. /*
  189. Run tests for the disassembler.
  190. */
  191. static bool test_dis()
  192. {
  193. // TODO
  194. return true;
  195. }
  196. /*
  197. Run integration tests (i.e., multiple components working together).
  198. */
  199. static bool test_integrate()
  200. {
  201. // TODO
  202. return true;
  203. }
  204. /*
  205. Main function.
  206. */
  207. int main(int argc, char *argv[])
  208. {
  209. if (argc != 2)
  210. FATAL("a single component name is required")
  211. const char *component = argv[1], *name;
  212. bool (*func)();
  213. if (!strcmp(component, "cpu")) {
  214. name = "Z80 CPU";
  215. func = test_cpu;
  216. } else if (!strcmp(component, "vdp")) {
  217. name = "VDP";
  218. func = test_vdp;
  219. } else if (!strcmp(component, "psg")) {
  220. name = "SN76489 PSG";
  221. func = test_psg;
  222. } else if (!strcmp(component, "asm")) {
  223. name = "assembler";
  224. func = test_asm;
  225. } else if (!strcmp(component, "dis")) {
  226. name = "disassembler";
  227. func = test_dis;
  228. } else if (!strcmp(component, "integrate")) {
  229. name = "integration";
  230. func = test_integrate;
  231. } else {
  232. FATAL("unknown component: %s", component)
  233. }
  234. printf("crater: running %s tests\n", name);
  235. atexit(finalize);
  236. return func() ? EXIT_SUCCESS : EXIT_FAILURE;
  237. }