Merge branch 'writing_tests'

This commit is contained in:
Nick Mathewson 2015-05-07 15:29:56 -04:00
commit e086db7952
8 changed files with 258 additions and 6 deletions

167
doc/WritingTests.txt Normal file
View File

@ -0,0 +1,167 @@
Writing tests for Tor: an incomplete guide
==========================================
Tor uses a variety of testing frameworks and methodologies to try to
keep from introducing bugs. The major ones are:
1. Unit tests written in C and shipped with the Tor distribution.
2. Integration tests written in Python and shipped with the Tor
distribution.
3. Integration tests written in Python and shipped with the Stem
library. Some of these use the Tor controller protocol.
4. System tests written in Python and SH, and shipped with the
Chutney package. These work by running many instances of Tor
locally, and sending traffic through them.
5. The Shadow network simulator.
How to run these tests
----------------------
=== The easy version
To run all the tests that come bundled with Tor, run "make check"
To run the Stem tests as well, fetch stem from the git repository,
set STEM_SOURCE_DIR to the checkout, and run "make test-stem".
To run the Chutney tests as well, fetch chutney from the git repository,
set CHUTNEY_PATH to the checkout, and run "make test-network".
=== Running particular subtests
XXXX WRITEME
=== Finding test coverage
When you configure Tor with the --enable-coverage option, it should
build with support for coverage in the unit tests, and in a special
"tor-cov" binary. If you launch
XXXX "make test-network" doesn't know about "tor-cov"; you don't get
XXXX coverage from that yet, unless you do "cp src/or/tor-cov
XXXX src/or/tor" before you run it.
What kinds of test should I write?
----------------------------------
XXXX writeme.
Unit and regression tests: Does this function do what it's supposed to?
-----------------------------------------------------------------------
Most of Tor's unit tests are made using the "tinytest" testing framework.
You can see a guide to using it in the tinytest manual at
https://github.com/nmathewson/tinytest/blob/master/tinytest-manual.md
To add a new test of this kind, either edit an existing C file in src/test/,
or create a new C file there. Each test is a single function that must
be indexed in the table at the end of the file. We use the label "done:" as
a cleanup point for all test functions.
(Make sure you read tinytest-manual.md before proceeding.)
I use the term "unit test" and "regression tests" very sloppily here.
=== A simple example
Here's an example of a test function for a simple function in util.c:
static void
test_util_writepid(void *arg)
{
(void) arg;
char *contents = NULL;
const char *fname = get_fname("tmp_pid");
unsigned long pid;
char c;
write_pidfile(fname);
contents = read_file_to_str(fname, 0, NULL);
tt_assert(contents);
int n = sscanf(contents, "%lu\n%c", &pid, &c);
tt_int_op(n, OP_EQ, 1);
tt_int_op(pid, OP_EQ, getpid());
done:
tor_free(contents);
}
This should look pretty familier to you if you've read the tinytest
manual. One thing to note here is that we use the testing-specific
function "get_fname" to generate a file with respect to a temporary
directory that the tests use. You don't need to delete the file;
it will get removed when the tests are done.
Also note our use of OP_EQ instead of == in the tt_int_op() calls.
We define OP_* macros to use instead of the binary comparison
operators so that analysis tools can more easily parse our code.
(Coccinelle really hates to see == used as a macro argument.)
Finally, remember that by convention, all *_free() functions that
Tor defines are defined to accept NULL harmlessly. Thus, you don't
need to say "if (contents)" in the cleanup block.
=== Exposing static functions for testing
Sometimes you need to test a function, but you don't want to expose
it outside its usual module.
To support this, Tor's build system compiles a testing version of
teach module, with extra identifiers exposed. If you want to
declare a function as static but available for testing, use the
macro "STATIC" instead of "static." Then, make sure there's a
macro-protected declaration of the function in the module's header.
For example, crypto_curve25519.h contains:
#ifdef CRYPTO_CURVE25519_PRIVATE
STATIC int curve25519_impl(uint8_t *output, const uint8_t *secret,
const uint8_t *basepoint);
#endif
The crypto_curve25519.c file and the test_crypto.c file both define
CRYPTO_CURVE25519_PRIVATE, so they can see this declaration.
=== Mock functions for testing in isolation
Often we want to test that a function works right, but the function depends
on other functions whose behavior is hard to observe, or whose
XXXX WRITEME
=== Advanced techniques: Namespaces
XXXX write this. danah boyd made us some really awesome stuff here.
Integration tests: Calling Tor from the outside
-----------------------------------------------
XXXX WRITEME
Writing integration tests with Stem
-----------------------------------
XXXX WRITEME
System testing with Chutney
---------------------------
XXXX WRITEME
Who knows what evil lurks in the timings of networks? The Shadow knows!
-----------------------------------------------------------------------
XXXX WRITEME

View File

@ -2092,8 +2092,8 @@ tor_tls_free(tor_tls_t *tls)
* number of characters read. On failure, returns TOR_TLS_ERROR,
* TOR_TLS_CLOSE, TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE.
*/
int
tor_tls_read(tor_tls_t *tls, char *cp, size_t len)
MOCK_IMPL(int,
tor_tls_read,(tor_tls_t *tls, char *cp, size_t len))
{
int r, err;
tor_assert(tls);

View File

@ -77,7 +77,7 @@ int tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity);
int tor_tls_check_lifetime(int severity,
tor_tls_t *tls, int past_tolerance,
int future_tolerance);
int tor_tls_read(tor_tls_t *tls, char *cp, size_t len);
MOCK_DECL(int, tor_tls_read, (tor_tls_t *tls, char *cp, size_t len));
int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n);
int tor_tls_handshake(tor_tls_t *tls);
int tor_tls_finish_handshake(tor_tls_t *tls);

View File

@ -3555,7 +3555,7 @@ finish_daemon(const char *cp)
/** Write the current process ID, followed by NL, into <b>filename</b>.
*/
void
write_pidfile(char *filename)
write_pidfile(const char *filename)
{
FILE *pidfile;

View File

@ -418,7 +418,7 @@ int path_is_relative(const char *filename);
/* Process helpers */
void start_daemon(void);
void finish_daemon(const char *desired_cwd);
void write_pidfile(char *filename);
void write_pidfile(const char *filename);
/* Port forwarding */
void tor_check_port_forwarding(const char *filename,

View File

@ -615,7 +615,7 @@ read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf)
if (r < 0)
return r; /* Error */
tor_assert(total_read+r < INT_MAX);
total_read += r;
total_read += r;
if ((size_t)r < readlen) /* eof, block, or no more to read. */
break;
}

View File

@ -698,6 +698,59 @@ test_buffers_zlib_fin_at_chunk_end(void *arg)
tor_free(msg);
}
const uint8_t *tls_read_ptr;
int n_remaining;
int next_reply_val[16];
static int
mock_tls_read(tor_tls_t *tls, char *cp, size_t len)
{
(void)tls;
int rv = next_reply_val[0];
if (rv > 0) {
int max = rv > (int)len ? (int)len : rv;
if (max > n_remaining)
max = n_remaining;
memcpy(cp, tls_read_ptr, max);
rv = max;
n_remaining -= max;
tls_read_ptr += max;
}
memmove(next_reply_val, next_reply_val + 1, 15*sizeof(int));
return rv;
}
static void
test_buffers_tls_read_mocked(void *arg)
{
uint8_t *mem;
buf_t *buf;
(void)arg;
mem = tor_malloc(64*1024);
crypto_rand((char*)mem, 64*1024);
tls_read_ptr = mem;
n_remaining = 64*1024;
MOCK(tor_tls_read, mock_tls_read);
buf = buf_new();
next_reply_val[0] = 1024;
tt_int_op(128, ==, read_to_buf_tls(NULL, 128, buf));
next_reply_val[0] = 5000;
next_reply_val[1] = 5000;
tt_int_op(6000, ==, read_to_buf_tls(NULL, 6000, buf));
done:
UNMOCK(tor_tls_read);
tor_free(mem);
buf_free(buf);
}
struct testcase_t buffer_tests[] = {
{ "basic", test_buffers_basic, TT_FORK, NULL, NULL },
{ "copy", test_buffer_copy, TT_FORK, NULL, NULL },
@ -710,6 +763,8 @@ struct testcase_t buffer_tests[] = {
{ "zlib_fin_with_nil", test_buffers_zlib_fin_with_nil, TT_FORK, NULL, NULL },
{ "zlib_fin_at_chunk_end", test_buffers_zlib_fin_at_chunk_end, TT_FORK,
NULL, NULL},
{ "tls_read_mocked", test_buffers_tls_read_mocked, 0,
NULL, NULL },
END_OF_TESTCASES
};

View File

@ -4302,6 +4302,35 @@ test_util_ipv4_validation(void *arg)
return;
}
static void
test_util_writepid(void *arg)
{
(void) arg;
char *contents = NULL;
const char *fname = get_fname("tmp_pid");
unsigned long pid;
char c;
write_pidfile(fname);
contents = read_file_to_str(fname, 0, NULL);
tt_assert(contents);
int n = sscanf(contents, "%lu\n%c", &pid, &c);
tt_int_op(n, OP_EQ, 1);
tt_uint_op(pid, OP_EQ,
#ifdef _WIN32
_getpid()
#else
getpid()
#endif
);
done:
tor_free(contents);
}
struct testcase_t util_tests[] = {
UTIL_LEGACY(time),
UTIL_TEST(parse_http_time, 0),
@ -4368,6 +4397,7 @@ struct testcase_t util_tests[] = {
UTIL_TEST(max_mem, 0),
UTIL_TEST(hostname_validation, 0),
UTIL_TEST(ipv4_validation, 0),
UTIL_TEST(writepid, 0),
END_OF_TESTCASES
};