aboutsummaryrefslogtreecommitdiff
path: root/test_harness/test_harness.c
blob: 2bd6194fc6840f8f35f631804b2081b79e0c86d4 (plain) (blame)
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include "test_harness.h"

#include <assert.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdarg.h>

#include "fake_env.h"

static jmp_buf jmpbuf;

volatile test_t dummy __attribute((__section__("tests")))
__attribute((__used__)) = {"dummy", "dummy", NULL};

extern unsigned char __data_start;
extern unsigned char _end;
extern test_t __start_tests;
extern test_t __stop_tests;

test_t* iter = &__start_tests;

static int execute_test(test_t* test);

void test_printll(size_t sz, long long v1, long long v2)
{
  fprintf(stderr, "%lld == %lld\n", v1, v2);
}
void test_printul(size_t sz, unsigned long v1, unsigned long v2)
{
  fprintf(stderr, "%lu == %lu\n", v1, v2);
}
void test_printd(size_t sz, int v1, int v2)
{
  fprintf(stderr, "%d == %d\n", v1, v2);
}
void test_printl(size_t sz, long v1, long v2)
{
  fprintf(stderr, "%lu == %lu\n", v1, v2);
}
void test_printui(size_t sz, unsigned int v1, unsigned int v2)
{
  fprintf(stderr, "%u == %u\n", v1, v2);
}
void test_prints(size_t sz, short v1, short v2)
{
  fprintf(stderr, "%hu == %hu\n", v1, v2);
}
void test_printus(size_t sz, unsigned short v1, unsigned short v2)
{
  fprintf(stderr, "%hu == %hu\n", v1, v2);
}
void test_printc(size_t sz, char v1, char v2)
{
  fprintf(stderr, "'%c' == '%c'\n", v1, v2);
}
void test_printf(size_t sz, double v1, double v2)
{
  fprintf(stderr, "%f == %f\n", v1, v2);
}
void test_printp(size_t sz, void* v1, void* v2)
{
  fprintf(stderr, "%p == %p\n", v1, v2);
}
void test_printuc(size_t sz, unsigned char v1, unsigned char v2)
{
  fprintf(stderr, "%02x == %02x\n", (int)v1, (int)v2);
}

static int do_fork = 1;
static size_t saved_data_size;
static unsigned char* saved_data = NULL;

int main(int argc, char** argv)
{
  /* Save all initialized data. */
  saved_data_size = &_end - &__data_start;
  saved_data = malloc(saved_data_size);
  memcpy(saved_data, &__data_start, saved_data_size);

  if (argc > 1 && strcmp(argv[1], "--nofork") == 0) {
    do_fork = 0;
  }

  for (; iter < &__stop_tests; ++iter) {
    if (iter->fn_ptr != NULL) {
      execute_test(iter);
    }
  }
}

void test_harness_abort(int ec)
{
  longjmp(jmpbuf, ec);
  assert("Long jump failed.\n");
}

/*
 * When nofork is true, this function will be called after each
 * test to try and make each test hermetic.
 *
 * It does this by reseting the data segment to what it was when
 * the program was first initialized.
 *
 * Of course, without forking, there's no way to guarantee hermetic
 * testing in C. In fact a simple segementation fault will break
 * the hermetic testing, but this does a pretty good job of at least
 * reseting the environment so tests don't directly depend on eachother.
 */
static void nofork_reset()
{
  wipeout_fake_env();

  /* Reset the data segment to what it was before. */
  memcpy(&__data_start, saved_data, saved_data_size);
}

void panic(const char* fmt, ...)
{
  va_list l;
  va_start(l, fmt);

  fprintf(stderr, "Kernel panic detected.");
  vfprintf(stderr, fmt, l);

  ASSERT_TRUE(0);
}

static int execute_test(test_t* test)
{
  char fullname[512];
  int status;
  int ec = 0;
  pid_t pid;

  snprintf(
      fullname, sizeof(fullname), "%s::%s", iter->test_suite, iter->test_name);

  if (!do_fork) {
    if ((ec = setjmp(jmpbuf)) == 0) {
      test->fn_ptr();
      printf(GREEN "[PASS]" RESET " %s\n", fullname);
      nofork_reset();
      return 0;
    } else {
      printf(RED "[FAIL] (%d)" RESET " %s\n", ec, fullname);
      nofork_reset();
      return ec;
    }
  }

  if (!(pid = fork())) {
    // child

    if ((ec = setjmp(jmpbuf))) {
      exit(ec);
    } else {
      test->fn_ptr();
      exit(0);
    }
  } else {
    if (waitpid(pid, &status, 0) == -1) {
      fprintf(stderr, "waitpid() failed\n");
      return 1;
    }

    if (WIFEXITED(status)) {
      switch ((ec = WEXITSTATUS(status))) {
        case 0:
          printf(GREEN "[PASS]" RESET " %s\n", fullname);
          return 0;
        default:
          printf(RED "[FAIL] (%d)" RESET " %s\n", ec, fullname);
          return ec;
      }
    } else if (WIFSIGNALED(status)) {
      int ec = WTERMSIG(status);
      printf("%s " RED "[FAIL] signaled %d" RESET "\n", fullname, ec);
      return ec;
    }

    return ec;
  }
}