summaryrefslogtreecommitdiffstats
path: root/src/tests/setjmp_test.c
blob: deafcee092bbaebda291e25b678639e026b587d4 (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
/*
 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * You can also choose to distribute this program under the terms of
 * the Unmodified Binary Distribution Licence (as given in the file
 * COPYING.UBDL), provided that you have satisfied its requirements.
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

/** @file
 *
 * setjmp()/longjmp() tests
 *
 */

/* Forcibly enable assertions */
#undef NDEBUG

#include <stddef.h>
#include <assert.h>
#include <setjmp.h>
#include <ipxe/test.h>

/** A setjmp()/longjmp() test */
struct setjmp_test {
	/** Jump buffer */
	jmp_buf env;
	/** Expected value */
	int expected;
	/** Test code file */
	const char *file;
	/** Test code line */
	unsigned int line;
};

/** Expected jump */
static struct setjmp_test *jumped;

/**
 * Report a setjmp() test result
 *
 * @v test		setjmp()/longjmp() test
 *
 * This has to be implemented as a macro since if it were a function
 * then the context saved by setjmp() would be invalidated when the
 * function returned.
 */
#define setjmp_ok( test ) do {						\
	int value;							\
	/* Sanity check */						\
	assert ( jumped == NULL );					\
	/* Initialise test */						\
	(test)->expected = 0;						\
	(test)->file = __FILE__;					\
	(test)->line = __LINE__;					\
	/* Perform setjmp() */						\
	value = setjmp ( (test)->env );					\
	/* Report setjmp()/longjmp() result */				\
	setjmp_return_ok ( (test), value );				\
	} while ( 0 )

/**
 * Report a setjmp()/longjmp() test result
 *
 * @v test		setjmp()/longjmp() test
 * @v value		Value returned from setjmp()
 *
 * This function ends up reporting results from either setjmp() or
 * longjmp() tests (since calls to longjmp() will return via the
 * corresponding setjmp()).  It therefore uses the test code file and
 * line stored in the test structure, which will represent the line
 * from which either setjmp() or longjmp() was called.
 */
static void setjmp_return_ok ( struct setjmp_test *test, int value ) {

	/* Determine whether this was reached via setjmp() or longjmp() */
	if ( value == 0 ) {
		/* This is the initial call to setjmp() */
		okx ( test->expected == 0, test->file, test->line );
		okx ( jumped == NULL, test->file, test->line );
	} else {
		/* This is reached via a call to longjmp() */
		okx ( value == test->expected, test->file, test->line );
		okx ( jumped == test, test->file, test->line );
	}

	/* Clear expected jump */
	jumped = NULL;
}

/**
 * Report a longjmp() test result
 *
 * @v test		setjmp()/longjmp() test
 * @v file		Test code file
 * @v line		Test code line
 */
static void __attribute__ (( noreturn ))
longjmp_okx ( struct setjmp_test *test, int value,
	      const char *file, unsigned int line ) {

	/* Record expected value.  A zero passed to longjmp() should
	 * result in setjmp() returning a value of one.
	 */
	test->expected = ( value ? value : 1 );

	/* Record test code file and line */
	test->file = file;
	test->line = line;

	/* Record expected jump */
	jumped = test;

	/* Perform longjmp().  Should return via setjmp_okx() */
	longjmp ( test->env, value );

	/* longjmp() should never return */
	assert ( 0 );
}
#define longjmp_ok( test, value ) \
	longjmp_okx ( test, value, __FILE__, __LINE__ )

/**
 * Perform setjmp()/longjmp() self-tests
 *
 */
static void setjmp_test_exec ( void ) {
	static struct setjmp_test alpha;
	static struct setjmp_test beta;
	static int iteration;

	/* This is one of the very few situations in which the
	 * "for-case" pattern is justified.
	 */
	for ( iteration = 0 ; iteration < 10 ; iteration++ ) {
		DBGC ( jumped, "SETJMP test iteration %d\n", iteration );
		switch ( iteration ) {
		case 0: setjmp_ok ( &alpha ); break;
		case 1: setjmp_ok ( &beta ); break;
		case 2:	longjmp_ok ( &alpha, 0 );
		case 3: longjmp_ok ( &alpha, 1 );
		case 4: longjmp_ok ( &alpha, 2 );
		case 5: longjmp_ok ( &beta, 17 );
		case 6: longjmp_ok ( &beta, 29 );
		case 7: longjmp_ok ( &alpha, -1 );
		case 8: longjmp_ok ( &beta, 0 );
		case 9: longjmp_ok ( &beta, 42 );
		}
	}
}

/** setjmp()/longjmp() self-test */
struct self_test setjmp_test __self_test = {
	.name = "setjmp",
	.exec = setjmp_test_exec,
};