File size: 3,763 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* Unity requires these, even if empty. */
void setUp(void) {
  /* Setup code here, or leave empty */
}
void tearDown(void) {
  /* Cleanup code here, or leave empty */
}

#ifdef EVAL_TRACE

/* Helper: capture the output of trace(fxn) given a specific args[].
   Returns a newly allocated NUL-terminated buffer on success, or NULL on error.
   Ensures stdout is restored before returning. Does not use Unity macros. */
static char *capture_trace_output(const char *fxn, char **test_args) {
  /* Save and set args for the duration of the call. */
  char ***args_ptr = &args; /* args is from expr.c (static char **args) */
  char **saved_args = *args_ptr;
  *args_ptr = test_args;

  /* Prepare a temporary FILE to redirect stdout. */
  FILE *tmp = tmpfile();
  if (!tmp) {
    *args_ptr = saved_args;
    return NULL;
  }

  fflush(stdout);
  int stdout_fd = fileno(stdout);
  int saved_fd = dup(stdout_fd);
  if (saved_fd == -1) {
    fclose(tmp);
    *args_ptr = saved_args;
    return NULL;
  }

  if (dup2(fileno(tmp), stdout_fd) == -1) {
    close(saved_fd);
    fclose(tmp);
    *args_ptr = saved_args;
    return NULL;
  }

  /* Call the function under test while stdout is redirected. */
  trace((char *)fxn);

  /* Flush everything to the temp file. */
  fflush(stdout);

  /* Determine size and read back. */
  long end = ftell(tmp);
  if (end < 0) {
    /* Restore stdout before returning. */
    dup2(saved_fd, stdout_fd);
    close(saved_fd);
    fclose(tmp);
    *args_ptr = saved_args;
    return NULL;
  }

  if (fseek(tmp, 0, SEEK_SET) != 0) {
    dup2(saved_fd, stdout_fd);
    close(saved_fd);
    fclose(tmp);
    *args_ptr = saved_args;
    return NULL;
  }

  char *buf = (char *)malloc((size_t)end + 1);
  if (!buf) {
    dup2(saved_fd, stdout_fd);
    close(saved_fd);
    fclose(tmp);
    *args_ptr = saved_args;
    return NULL;
  }

  size_t nread = fread(buf, 1, (size_t)end, tmp);
  buf[nread] = '\0';

  /* Restore stdout and cleanup. */
  fflush(stdout);
  dup2(saved_fd, stdout_fd);
  close(saved_fd);
  fclose(tmp);
  *args_ptr = saved_args;

  return buf;
}

static void assert_trace_equals(const char *fxn, char **test_args, const char *expected) {
  char *out = capture_trace_output(fxn, test_args);
  TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture trace output");
  TEST_ASSERT_EQUAL_STRING(expected, out);
  free(out);
}

void test_trace_empty_args(void) {
  char *a0[] = { NULL };
  assert_trace_equals("eval", a0, "eval:\n");
}

void test_trace_single_arg(void) {
  char *a1[] = { "foo", NULL };
  assert_trace_equals("walk", a1, "walk: foo\n");
}

void test_trace_multiple_args(void) {
  char *a2[] = { "arg1", "arg2", "arg3", NULL };
  assert_trace_equals("parse", a2, "parse: arg1 arg2 arg3\n");
}

void test_trace_empty_string_arg(void) {
  char *a3[] = { "", "X", NULL };
  /* Note the double space after ':' due to empty first argument. */
  assert_trace_equals("step", a3, "step:  X\n");
}

void test_trace_utf8_args(void) {
  /* UTF-8: "\xCE\xB1" is Greek alpha, "\xE2\x9D\xA7" is U+2767. */
  char *a4[] = { "\xCE\xB1bc", "\xE2\x9D\xA7", NULL };
  assert_trace_equals("mb", a4, "mb: \xCE\xB1bc \xE2\x9D\xA7\n");
}

#else /* !EVAL_TRACE */

void test_trace_unavailable(void) {
  TEST_IGNORE_MESSAGE("EVAL_TRACE not defined; trace() not compiled, skipping tests.");
}

#endif /* EVAL_TRACE */

int main(void) {
  UNITY_BEGIN();
#ifdef EVAL_TRACE
  RUN_TEST(test_trace_empty_args);
  RUN_TEST(test_trace_single_arg);
  RUN_TEST(test_trace_multiple_args);
  RUN_TEST(test_trace_empty_string_arg);
  RUN_TEST(test_trace_utf8_args);
#else
  RUN_TEST(test_trace_unavailable);
#endif
  return UNITY_END();
}