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.
 
 
 
 
 

275 lines
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. }