]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Uplift status.c unit test coverage with new test cases and macros.
authordana koch <dsk@google.com>
Tue, 15 Apr 2014 12:20:34 +0000 (22:20 +1000)
committerNick Mathewson <nickm@torproject.org>
Tue, 15 Apr 2014 19:00:34 +0000 (15:00 -0400)
A new set of unit test cases are provided, as well as introducing
an alternative paradigm and macros to support it. Primarily, each test
case is given its own namespace, in order to isolate tests from each
other. We do this by in the usual fashion, by appending module and
submodule names to our symbols. New macros assist by reducing friction
for this and other tasks, like overriding a function in the global
namespace with one in the current namespace, or declaring integer
variables to assist tracking how many times a mock has been called.

A set of tests for a small-scale module has been included in this
commit, in order to highlight how the paradigm can be used. This
suite gives 100% coverage to status.c in test execution.

23 files changed:
src/common/log.c
src/common/torlog.h
src/common/tortls.c
src/common/tortls.h
src/or/circuitlist.c
src/or/circuitlist.h
src/or/config.c
src/or/config.h
src/or/hibernate.c
src/or/hibernate.h
src/or/main.c
src/or/main.h
src/or/nodelist.c
src/or/nodelist.h
src/or/rephist.c
src/or/router.c
src/or/router.h
src/or/status.c
src/or/status.h
src/test/include.am
src/test/test.c
src/test/test.h
src/test/test_status.c [new file with mode: 0644]

index a837cf86a713a197e0a4ddca76b6a1b02d888ffa..592dc2c5d466c15347822ec45d71e85e7e0447e0 100644 (file)
@@ -147,9 +147,6 @@ static INLINE char *format_msg(char *buf, size_t buf_len,
            const char *suffix,
            const char *format, va_list ap, size_t *msg_len_out)
   CHECK_PRINTF(7,0);
-static void logv(int severity, log_domain_mask_t domain, const char *funcname,
-                 const char *suffix, const char *format, va_list ap)
-  CHECK_PRINTF(5,0);
 
 /** Name of the application: used to generate the message we write at the
  * start of each new log. */
@@ -336,9 +333,9 @@ format_msg(char *buf, size_t buf_len,
  * <b>severity</b>.  If provided, <b>funcname</b> is prepended to the
  * message.  The actual message is derived as from tor_snprintf(format,ap).
  */
-static void
-logv(int severity, log_domain_mask_t domain, const char *funcname,
-     const char *suffix, const char *format, va_list ap)
+MOCK_IMPL(STATIC void,
+logv,(int severity, log_domain_mask_t domain, const char *funcname,
+     const char *suffix, const char *format, va_list ap))
 {
   char buf[10024];
   size_t msg_len = 0;
index d210c8b24936764d53bf79e7564a3ca5c2a93359..4493b251d26cdc1d5b880cd29a50d1c9c6e114c8 100644 (file)
@@ -13,6 +13,7 @@
 #ifndef TOR_TORLOG_H
 
 #include "compat.h"
+#include "testsupport.h"
 
 #ifdef HAVE_SYSLOG_H
 #include <syslog.h>
@@ -228,6 +229,12 @@ extern const char *log_fn_function_name_;
 
 #endif /* !GNUC */
 
+#ifdef LOG_PRIVATE
+MOCK_DECL(STATIC void, logv, (int severity, log_domain_mask_t domain,
+    const char *funcname, const char *suffix, const char *format,
+    va_list ap) CHECK_PRINTF(5,0));
+#endif
+
 # define TOR_TORLOG_H
 #endif
 
index 5bf7cb304c78ed5c8a4eabaa55ca4d181c73b235..dbe8cdcef47fd2de7e09937fb69a261e55d21fc4 100644 (file)
@@ -2579,8 +2579,8 @@ tor_tls_get_n_raw_bytes(tor_tls_t *tls, size_t *n_read, size_t *n_written)
 
 /** Return a ratio of the bytes that TLS has sent to the bytes that we've told
  * it to send. Used to track whether our TLS records are getting too tiny. */
-double
-tls_get_write_overhead_ratio(void)
+MOCK_IMPL(double,
+tls_get_write_overhead_ratio,(void))
 {
   if (total_bytes_written_over_tls == 0)
     return 1.0;
index 49c488b365dccc50c1b1730e69a20e5da1b0c6ab..a76ba3bc7a9d3050ad3f211dee249321ce8d8bbf 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "crypto.h"
 #include "compat.h"
+#include "testsupport.h"
 
 /* Opaque structure to hold a TLS connection. */
 typedef struct tor_tls_t tor_tls_t;
@@ -95,7 +96,7 @@ void tor_tls_get_buffer_sizes(tor_tls_t *tls,
                               size_t *rbuf_capacity, size_t *rbuf_bytes,
                               size_t *wbuf_capacity, size_t *wbuf_bytes);
 
-double tls_get_write_overhead_ratio(void);
+MOCK_DECL(double, tls_get_write_overhead_ratio, (void));
 
 int tor_tls_used_v1_handshake(tor_tls_t *tls);
 int tor_tls_received_v3_certificate(tor_tls_t *tls);
index 87270037c718eccb8cce304ff8233e7ec3a2ff7b..b71dc3c13a50f1033c64c72a2eab288c539ea10e 100644 (file)
@@ -438,8 +438,8 @@ circuit_close_all_marked(void)
 }
 
 /** Return the head of the global linked list of circuits. */
-struct global_circuitlist_s *
-circuit_get_global_list(void)
+MOCK_IMPL(struct global_circuitlist_s *,
+circuit_get_global_list,(void))
 {
   return &global_circuitlist;
 }
index 2bbd20b83a994fd7f18b45fd3bb8b652cba60c25..916afba215ee2f5b5cd29b2673db1fc7973bedc5 100644 (file)
@@ -16,7 +16,7 @@
 
 TOR_LIST_HEAD(global_circuitlist_s, circuit_t);
 
-struct global_circuitlist_s* circuit_get_global_list(void);
+MOCK_DECL(struct global_circuitlist_s*, circuit_get_global_list, (void));
 const char *circuit_state_to_string(int state);
 const char *circuit_purpose_to_controller_string(uint8_t purpose);
 const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose);
index dbf643c53a732dfba224adcd96aa47f4f3ce6b5c..ca99d014fc208ec82638e7d838c9b32ebb5986da 100644 (file)
@@ -620,8 +620,8 @@ get_options_mutable(void)
 }
 
 /** Returns the currently configured options */
-const or_options_t *
-get_options(void)
+MOCK_IMPL(const or_options_t *,
+get_options,(void))
 {
   return get_options_mutable();
 }
index 8ee2a457259d856089309d3970ca3cb82ff39ec1..bf386134b8189cc16b3b0dec87c2f987c8410f14 100644 (file)
 #ifndef TOR_CONFIG_H
 #define TOR_CONFIG_H
 
+#include "testsupport.h"
+
 const char *get_dirportfrontpage(void);
-const or_options_t *get_options(void);
+MOCK_DECL(const or_options_t *,get_options,(void));
 or_options_t *get_options_mutable(void);
 int set_options(or_options_t *new_val, char **msg);
 void config_free_all(void);
index bbda8424f667807754460148e6bf8307de2d9beb..c433ac1be928ab180dfe58d700127000bdb051dd 100644 (file)
@@ -239,8 +239,8 @@ accounting_parse_options(const or_options_t *options, int validate_only)
 /** If we want to manage the accounting system and potentially
  * hibernate, return 1, else return 0.
  */
-int
-accounting_is_enabled(const or_options_t *options)
+MOCK_IMPL(int,
+accounting_is_enabled,(const or_options_t *options))
 {
   if (options->AccountingMax)
     return 1;
@@ -256,8 +256,8 @@ accounting_get_interval_length(void)
 }
 
 /** Return the time at which the current accounting interval will end. */
-time_t
-accounting_get_end_time(void)
+MOCK_IMPL(time_t,
+accounting_get_end_time,(void))
 {
   return interval_end_time;
 }
@@ -823,8 +823,8 @@ hibernate_begin_shutdown(void)
 }
 
 /** Return true iff we are currently hibernating. */
-int
-we_are_hibernating(void)
+MOCK_IMPL(int,
+we_are_hibernating,(void))
 {
   return hibernate_state != HIBERNATE_STATE_LIVE;
 }
index 4f7331ce8ca3602b5ff5d14987c8e2785b087b39..38ecb75129cde508a05bc687402688ea91a3b347 100644 (file)
 #ifndef TOR_HIBERNATE_H
 #define TOR_HIBERNATE_H
 
+#include "testsupport.h"
+
 int accounting_parse_options(const or_options_t *options, int validate_only);
-int accounting_is_enabled(const or_options_t *options);
+MOCK_DECL(int, accounting_is_enabled, (const or_options_t *options));
 int accounting_get_interval_length(void);
-time_t accounting_get_end_time(void);
+MOCK_DECL(time_t, accounting_get_end_time, (void));
 void configure_accounting(time_t now);
 void accounting_run_housekeeping(time_t now);
 void accounting_add_bytes(size_t n_read, size_t n_written, int seconds);
 int accounting_record_bandwidth_usage(time_t now, or_state_t *state);
 void hibernate_begin_shutdown(void);
-int we_are_hibernating(void);
+MOCK_DECL(int, we_are_hibernating, (void));
 void consider_hibernation(time_t now);
 int getinfo_helper_accounting(control_connection_t *conn,
                               const char *question, char **answer,
index c6619fe7a43086518e55d9eced8b4a27d0266830..0264064edcc7428a11d62d0ce2ef73b84ea4979c 100644 (file)
@@ -469,15 +469,15 @@ get_connection_array(void)
 
 /** Provides the traffic read and written over the life of the process. */
 
-uint64_t
-get_bytes_read(void)
+MOCK_IMPL(uint64_t,
+get_bytes_read,(void))
 {
   return stats_n_bytes_read;
 }
 
 /* DOCDOC get_bytes_written */
-uint64_t
-get_bytes_written(void)
+MOCK_IMPL(uint64_t,
+get_bytes_written,(void))
 {
   return stats_n_bytes_written;
 }
@@ -2121,8 +2121,8 @@ process_signal(uintptr_t sig)
 }
 
 /** Returns Tor's uptime. */
-long
-get_uptime(void)
+MOCK_IMPL(long,
+get_uptime,(void))
 {
   return stats_n_seconds_working;
 }
index a2f03d9546e91fb0fb8a3fee8c6d3c6d901a5ef2..a3bce3486f5c7b986d39ee29c71bd90779f2f2aa 100644 (file)
@@ -24,8 +24,8 @@ void add_connection_to_closeable_list(connection_t *conn);
 int connection_is_on_closeable_list(connection_t *conn);
 
 smartlist_t *get_connection_array(void);
-uint64_t get_bytes_read(void);
-uint64_t get_bytes_written(void);
+MOCK_DECL(uint64_t,get_bytes_read,(void));
+MOCK_DECL(uint64_t,get_bytes_written,(void));
 
 /** Bitmask for events that we can turn on and off with
  * connection_watch_events. */
@@ -52,7 +52,8 @@ void ip_address_changed(int at_interface);
 void dns_servers_relaunch_checks(void);
 void reschedule_descriptor_update_check(void);
 
-long get_uptime(void);
+MOCK_DECL(long,get_uptime,(void));
+
 unsigned get_signewnym_epoch(void);
 
 void handle_signals(int is_parent);
index 52c92661c0c699cf457663e193d8087c540a63c7..a38a6d4993daea595130479505c47b889b43e146 100644 (file)
@@ -85,8 +85,8 @@ node_get_mutable_by_id(const char *identity_digest)
 
 /** Return the node_t whose identity is <b>identity_digest</b>, or NULL
  * if no such node exists. */
-const node_t *
-node_get_by_id(const char *identity_digest)
+MOCK_IMPL(const node_t *,
+node_get_by_id,(const char *identity_digest))
 {
   return node_get_mutable_by_id(identity_digest);
 }
index 95d0c232838e97e620d7fa68433ecefeb0c57542..8e719e012d2a409fce9e77aee1829cd00bd41f27 100644 (file)
@@ -17,7 +17,7 @@
   } STMT_END
 
 node_t *node_get_mutable_by_id(const char *identity_digest);
-const node_t *node_get_by_id(const char *identity_digest);
+MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest));
 const node_t *node_get_by_hex_id(const char *identity_digest);
 node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out);
 node_t *nodelist_add_microdesc(microdesc_t *md);
index 87f930a28d6fef94c61b68149e8b97bfc722c068..70be39e23075cf7bcc09b962eb77c05acc8281c5 100644 (file)
@@ -2995,8 +2995,8 @@ rep_hist_conn_stats_write(time_t now)
  * handshake we've received, and how many we've assigned to cpuworkers.
  * Useful for seeing trends in cpu load.
  * @{ */
-static int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
-static int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
+STATIC int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
+STATIC int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1] = {0};
 /**@}*/
 
 /** A new onionskin (using the <b>type</b> handshake) has arrived. */
index 389120be049b3fc93691baf7c7cb969854f980ed..86cefc9a6f8df8f3cf22693079b4e605d83ead59 100755 (executable)
@@ -1348,8 +1348,8 @@ authdir_mode_bridge(const or_options_t *options)
 
 /** Return true iff we are trying to be a server.
  */
-int
-server_mode(const or_options_t *options)
+MOCK_IMPL(int,
+server_mode,(const or_options_t *options))
 {
   if (options->ClientOnly) return 0;
   /* XXXX024 I believe we can kill off ORListenAddress here.*/
@@ -1358,8 +1358,8 @@ server_mode(const or_options_t *options)
 
 /** Return true iff we are trying to be a non-bridge server.
  */
-int
-public_server_mode(const or_options_t *options)
+MOCK_IMPL(int,
+public_server_mode,(const or_options_t *options))
 {
   if (!server_mode(options)) return 0;
   return (!options->BridgeRelay);
@@ -1689,8 +1689,8 @@ router_is_me(const routerinfo_t *router)
 
 /** Return a routerinfo for this OR, rebuilding a fresh one if
  * necessary.  Return NULL on error, or if called on an OP. */
-const routerinfo_t *
-router_get_my_routerinfo(void)
+MOCK_IMPL(const routerinfo_t *,
+router_get_my_routerinfo,(void))
 {
   if (!server_mode(get_options()))
     return NULL;
index 28e1ed6e6b3e7b35b4a021cedb0ba7270eca4641..d18ff065ea630e25502c03e1011cf0c2ab0470e7 100644 (file)
@@ -66,8 +66,8 @@ uint16_t router_get_advertised_or_port_by_af(const or_options_t *options,
 uint16_t router_get_advertised_dir_port(const or_options_t *options,
                                         uint16_t dirport);
 
-int server_mode(const or_options_t *options);
-int public_server_mode(const or_options_t *options);
+MOCK_DECL(int, server_mode, (const or_options_t *options));
+MOCK_DECL(int, public_server_mode, (const or_options_t *options));
 int advertised_server_mode(void);
 int proxy_mode(const or_options_t *options);
 void consider_publishable_server(int force);
@@ -82,7 +82,7 @@ void router_new_address_suggestion(const char *suggestion,
                                    const dir_connection_t *d_conn);
 int router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port);
 int router_my_exit_policy_is_reject_star(void);
-const routerinfo_t *router_get_my_routerinfo(void);
+MOCK_DECL(const routerinfo_t *, router_get_my_routerinfo, (void));
 extrainfo_t *router_get_my_extrainfo(void);
 const char *router_get_my_descriptor(void);
 const char *router_get_descriptor_gen_reason(void);
index e1820c888996bfdbc20e2e496f7b43ec4e58b875..7e2afbce8006598903d3253c205d0fa230bf0a8b 100644 (file)
@@ -6,6 +6,8 @@
  * \brief Keep status information and log the heartbeat messages.
  **/
 
+#define STATUS_PRIVATE
+
 #include "or.h"
 #include "config.h"
 #include "status.h"
@@ -22,7 +24,7 @@
 static void log_accounting(const time_t now, const or_options_t *options);
 
 /** Return the total number of circuits. */
-static int
+STATIC int
 count_circuits(void)
 {
   circuit_t *circ;
@@ -36,7 +38,7 @@ count_circuits(void)
 
 /** Take seconds <b>secs</b> and return a newly allocated human-readable
  * uptime string */
-static char *
+STATIC char *
 secs_to_uptime(long secs)
 {
   long int days = secs / 86400;
@@ -63,7 +65,7 @@ secs_to_uptime(long secs)
 
 /** Take <b>bytes</b> and returns a newly allocated human-readable usage
  * string. */
-static char *
+STATIC char *
 bytes_to_usage(uint64_t bytes)
 {
   char *bw_string = NULL;
index 7c3b969c8687d6a2c79fa8b0e08e88ecce847286..13458ea4762af2409a1c31b899d788ccd256c34f 100644 (file)
@@ -4,7 +4,15 @@
 #ifndef TOR_STATUS_H
 #define TOR_STATUS_H
 
+#include "testsupport.h"
+
 int log_heartbeat(time_t now);
 
+#ifdef STATUS_PRIVATE
+STATIC int count_circuits(void);
+STATIC char *secs_to_uptime(long secs);
+STATIC char *bytes_to_usage(uint64_t bytes);
+#endif
+
 #endif
 
index 34bbe6b5e9b032b01f96f43f40b136603fb760d7..fba439a6167b362e41e75aa69b5b9350e6963397 100644 (file)
@@ -44,6 +44,7 @@ src_test_test_SOURCES = \
        src/test/test_hs.c \
        src/test/test_nodelist.c \
        src/test/test_policy.c \
+       src/test/test_status.c \
        src/ext/tinytest.c
 
 src_test_test_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
index e82f323a9aea8043cbb0a6acb38bdc7c8f14c279..771725e231d05cb790ab4aa5e2698203f7cf9ce4 100644 (file)
@@ -1300,6 +1300,7 @@ extern struct testcase_t nodelist_tests[];
 extern struct testcase_t routerkeys_tests[];
 extern struct testcase_t oom_tests[];
 extern struct testcase_t policy_tests[];
+extern struct testcase_t status_tests[];
 
 static struct testgroup_t testgroups[] = {
   { "", test_array },
@@ -1329,6 +1330,7 @@ static struct testgroup_t testgroups[] = {
   { "routerkeys/", routerkeys_tests },
   { "oom/", oom_tests },
   { "policy/" , policy_tests },
+  { "status/" , status_tests },
   END_OF_GROUPS
 };
 
index ba82f52add20f4a32ca5b4a7ee2858e11f986f22..0ccf6c718ea1d43f7d33df2cf0556b982fa74c45 100644 (file)
@@ -65,5 +65,97 @@ crypto_pk_t *pk_generate(int idx);
 void legacy_test_helper(void *data);
 extern const struct testcase_setup_t legacy_setup;
 
+#define US2_CONCAT_2__(a, b) a ## __ ## b
+#define US_CONCAT_2__(a, b) a ## _ ## b
+#define US_CONCAT_3__(a, b, c) a ## _ ## b ## _ ## c
+#define US_CONCAT_2_(a, b) US_CONCAT_2__(a, b)
+#define US_CONCAT_3_(a, b, c) US_CONCAT_3__(a, b, c)
+
+/*
+ * These macros are helpful for streamlining the authorship of several test
+ * cases that use mocks.
+ *
+ * The pattern is as follows.
+ * * Declare a top level namespace:
+ *       #define NS_MODULE foo
+ *
+ * * For each test case you want to write, create a new submodule in the
+ *   namespace. All mocks and other information should belong to a single
+ *   submodule to avoid interference with other test cases.
+ *   You can simply name the submodule after the function in the module you
+ *   are testing:
+ *       #define NS_SUBMODULE some_function
+ *   or, if you're wanting to write several tests against the same function,
+ *   ie., you are testing an aspect of that function, you can use:
+ *       #define NS_SUBMODULE ASPECT(some_function, behavior)
+ *
+ * * Declare all the mocks you will use. The NS_DECL macro serves to declare
+ *   the mock in the current namespace (defined by NS_MODULE and NS_SUBMODULE).
+ *   It behaves like MOCK_DECL:
+ *       NS_DECL(int, dependent_function, (void *));
+ *   Here, dependent_function must be declared and implemented with the
+ *   MOCK_DECL and MOCK_IMPL macros. The NS_DECL macro also defines an integer
+ *   global for use for tracking how many times a mock was called, and can be
+ *   accessed by CALLED(mock_name). For example, you might put
+ *       CALLED(dependent_function)++;
+ *   in your mock body.
+ *
+ * * Define a function called NS(main) that will contain the body of the
+ *   test case. The NS macro can be used to reference a name in the current
+ *   namespace.
+ *
+ * * In NS(main), indicate that a mock function in the current namespace,
+ *   declared with NS_DECL is to override that in the global namespace,
+ *   with the NS_MOCK macro:
+ *       NS_MOCK(dependent_function)
+ *   Unmock with:
+ *       NS_UNMOCK(dependent_function)
+ *
+ * * Define the mocks with the NS macro, eg.,
+ *       int
+ *       NS(dependent_function)(void *)
+ *       {
+ *           CALLED(dependent_function)++;
+ *       }
+ *
+ * * In the struct testcase_t array, you can use the TEST_CASE and
+ *   TEST_CASE_ASPECT macros to define the cases without having to do so
+ *   explicitly nor without having to reset NS_SUBMODULE, eg.,
+ *       struct testcase_t foo_tests[] = {
+ *         TEST_CASE_ASPECT(some_function, behavior),
+ *         ...
+ *         END_OF_TESTCASES
+ *   which will define a test case named "some_function__behavior".
+ */
+
+#define NAME_TEST_(name) #name
+#define NAME_TEST(name) NAME_TEST_(name)
+#define ASPECT(test_module, test_name) US2_CONCAT_2__(test_module, test_name)
+#define TEST_CASE(function)  \
+  {  \
+      NAME_TEST(function),  \
+      NS_FULL(NS_MODULE, function, test_main),  \
+      TT_FORK,  \
+      NULL,  \
+      NULL,  \
+  }
+#define TEST_CASE_ASPECT(function, aspect)  \
+  {  \
+      NAME_TEST(ASPECT(function, aspect)),  \
+      NS_FULL(NS_MODULE, ASPECT(function, aspect), test_main),  \
+      TT_FORK,  \
+      NULL,  \
+      NULL,  \
+  }
+
+#define NS(name) US_CONCAT_3_(NS_MODULE, NS_SUBMODULE, name)
+#define NS_FULL(module, submodule, name) US_CONCAT_3_(module, submodule, name)
+
+#define CALLED(mock_name) US_CONCAT_2_(NS(mock_name), called)
+#define NS_DECL(retval, mock_fn, args) \
+    static retval NS(mock_fn) args; int CALLED(mock_fn) = 0
+#define NS_MOCK(name) MOCK(name, NS(name))
+#define NS_UNMOCK(name) UNMOCK(name)
+
 #endif
 
diff --git a/src/test/test_status.c b/src/test/test_status.c
new file mode 100644 (file)
index 0000000..09c9935
--- /dev/null
@@ -0,0 +1,1109 @@
+#define STATUS_PRIVATE
+#define HIBERNATE_PRIVATE
+#define LOG_PRIVATE
+#define REPHIST_PRIVATE
+
+#include <float.h>
+#include <math.h>
+
+#include "or.h"
+#include "torlog.h"
+#include "tor_queue.h"
+#include "status.h"
+#include "circuitlist.h"
+#include "config.h"
+#include "hibernate.h"
+#include "rephist.h"
+#include "relay.h"
+#include "router.h"
+#include "main.h"
+#include "nodelist.h"
+#include "statefile.h"
+#include "test.h"
+
+#define NS_MODULE status
+
+#define NS_SUBMODULE count_circuits
+
+/*
+ * Test that count_circuits() is correctly counting the number of
+ * global circuits.
+ */
+
+struct global_circuitlist_s mock_global_circuitlist =
+  TOR_LIST_HEAD_INITIALIZER(global_circuitlist);
+
+NS_DECL(struct global_circuitlist_s *, circuit_get_global_list, (void));
+
+static void
+NS(test_main)(void *arg)
+{
+  /* Choose origin_circuit_t wlog. */
+  origin_circuit_t *mock_circuit1, *mock_circuit2;
+  circuit_t *circ, *tmp;
+  int expected_circuits = 2, actual_circuits;
+
+  (void)arg;
+
+  mock_circuit1 = tor_malloc_zero(sizeof(origin_circuit_t));
+  mock_circuit2 = tor_malloc_zero(sizeof(origin_circuit_t));
+  TOR_LIST_INSERT_HEAD(
+    &mock_global_circuitlist, TO_CIRCUIT(mock_circuit1), head);
+  TOR_LIST_INSERT_HEAD(
+    &mock_global_circuitlist, TO_CIRCUIT(mock_circuit2), head);
+
+  NS_MOCK(circuit_get_global_list);
+
+  actual_circuits = count_circuits();
+
+  tt_assert(expected_circuits == actual_circuits);
+
+  done:
+    TOR_LIST_FOREACH_SAFE(
+        circ, NS(circuit_get_global_list)(), head, tmp);
+      tor_free(circ);
+    NS_UNMOCK(circuit_get_global_list);
+}
+
+static struct global_circuitlist_s *
+NS(circuit_get_global_list)(void)
+{
+  return &mock_global_circuitlist;
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE secs_to_uptime
+
+/*
+ * Test that secs_to_uptime() is converting the number of seconds that
+ * Tor is up for into the appropriate string form containing hours and minutes.
+ */
+
+static void
+NS(test_main)(void *arg)
+{
+  const char *expected;
+  char *actual;
+  (void)arg;
+
+  expected = "0:00 hours";
+  actual = secs_to_uptime(0);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "0:00 hours";
+  actual = secs_to_uptime(1);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "0:01 hours";
+  actual = secs_to_uptime(60);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "0:59 hours";
+  actual = secs_to_uptime(60 * 59);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1:00 hours";
+  actual = secs_to_uptime(60 * 60);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "23:59 hours";
+  actual = secs_to_uptime(60 * 60 * 23 + 60 * 59);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1 day 0:00 hours";
+  actual = secs_to_uptime(60 * 60 * 23 + 60 * 60);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1 day 0:00 hours";
+  actual = secs_to_uptime(86400 + 1);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1 day 0:01 hours";
+  actual = secs_to_uptime(86400 + 60);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "10 days 0:00 hours";
+  actual = secs_to_uptime(86400 * 10);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "10 days 0:00 hours";
+  actual = secs_to_uptime(864000 + 1);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "10 days 0:01 hours";
+  actual = secs_to_uptime(864000 + 60);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  done:
+    if (actual != NULL)
+      tor_free(actual);
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE bytes_to_usage
+
+/*
+ * Test that bytes_to_usage() is correctly converting the number of bytes that
+ * Tor has read/written into the appropriate string form containing kilobytes,
+ * megabytes, or gigabytes.
+ */
+
+static void
+NS(test_main)(void *arg)
+{
+  const char *expected;
+  char *actual;
+  (void)arg;
+
+  expected = "0 kB";
+  actual = bytes_to_usage(0);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "0 kB";
+  actual = bytes_to_usage(1);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1 kB";
+  actual = bytes_to_usage(1024);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1023 kB";
+  actual = bytes_to_usage((1 << 20) - 1);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1.00 MB";
+  actual = bytes_to_usage((1 << 20));
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1.00 MB";
+  actual = bytes_to_usage((1 << 20) + 5242);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1.01 MB";
+  actual = bytes_to_usage((1 << 20) + 5243);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1024.00 MB";
+  actual = bytes_to_usage((1 << 30) - 1);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1.00 GB";
+  actual = bytes_to_usage((1 << 30));
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1.00 GB";
+  actual = bytes_to_usage((1 << 30) + 5368709);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "1.01 GB";
+  actual = bytes_to_usage((1 << 30) + 5368710);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  expected = "10.00 GB";
+  actual = bytes_to_usage((1 << 30) * 10L);
+  tt_str_op(actual, ==, expected);
+  tor_free(actual);
+
+  done:
+    if (actual != NULL)
+      tor_free(actual);
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE ASPECT(log_heartbeat, fails)
+
+/*
+ * Tests that log_heartbeat() fails when in the public server mode,
+ * not hibernating, and we couldn't get the current routerinfo.
+ */
+
+NS_DECL(double, tls_get_write_overhead_ratio, (void));
+NS_DECL(int, we_are_hibernating, (void));
+NS_DECL(const or_options_t *, get_options, (void));
+NS_DECL(int, public_server_mode, (const or_options_t *options));
+NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void));
+
+static void
+NS(test_main)(void *arg)
+{
+  int expected, actual;
+  (void)arg;
+
+  NS_MOCK(tls_get_write_overhead_ratio);
+  NS_MOCK(we_are_hibernating);
+  NS_MOCK(get_options);
+  NS_MOCK(public_server_mode);
+  NS_MOCK(router_get_my_routerinfo);
+
+  expected = -1;
+  actual = log_heartbeat(0);
+
+  tt_int_op(actual, ==, expected);
+
+  done:
+    NS_UNMOCK(tls_get_write_overhead_ratio);
+    NS_UNMOCK(we_are_hibernating);
+    NS_UNMOCK(get_options);
+    NS_UNMOCK(public_server_mode);
+    NS_UNMOCK(router_get_my_routerinfo);
+}
+
+static double
+NS(tls_get_write_overhead_ratio)(void)
+{
+  return 2.0;
+}
+
+static int
+NS(we_are_hibernating)(void)
+{
+  return 0;
+}
+
+static const or_options_t *
+NS(get_options)(void)
+{
+  return NULL;
+}
+
+static int
+NS(public_server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 1;
+}
+
+static const routerinfo_t *
+NS(router_get_my_routerinfo)(void)
+{
+  return NULL;
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE ASPECT(log_heartbeat, not_in_consensus)
+
+/*
+ * Tests that log_heartbeat() logs appropriately if we are not in the cached
+ * consensus.
+ */
+
+NS_DECL(double, tls_get_write_overhead_ratio, (void));
+NS_DECL(int, we_are_hibernating, (void));
+NS_DECL(const or_options_t *, get_options, (void));
+NS_DECL(int, public_server_mode, (const or_options_t *options));
+NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void));
+NS_DECL(const node_t *, node_get_by_id, (const char *identity_digest));
+NS_DECL(void, logv, (int severity, log_domain_mask_t domain,
+    const char *funcname, const char *suffix, const char *format, va_list ap));
+NS_DECL(int, server_mode, (const or_options_t *options));
+
+static routerinfo_t *mock_routerinfo;
+extern int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1];
+extern int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1];
+
+static void
+NS(test_main)(void *arg)
+{
+  int expected, actual;
+  (void)arg;
+
+  NS_MOCK(tls_get_write_overhead_ratio);
+  NS_MOCK(we_are_hibernating);
+  NS_MOCK(get_options);
+  NS_MOCK(public_server_mode);
+  NS_MOCK(router_get_my_routerinfo);
+  NS_MOCK(node_get_by_id);
+  NS_MOCK(logv);
+  NS_MOCK(server_mode);
+
+  log_global_min_severity_ = LOG_DEBUG;
+  onion_handshakes_requested[ONION_HANDSHAKE_TYPE_TAP] = 1;
+  onion_handshakes_assigned[ONION_HANDSHAKE_TYPE_TAP] = 1;
+  onion_handshakes_requested[ONION_HANDSHAKE_TYPE_NTOR] = 1;
+  onion_handshakes_assigned[ONION_HANDSHAKE_TYPE_NTOR] = 1;
+
+  expected = 0;
+  actual = log_heartbeat(0);
+
+  tt_int_op(actual, ==, expected);
+  tt_int_op(CALLED(logv), ==, 3);
+
+  done:
+    NS_UNMOCK(tls_get_write_overhead_ratio);
+    NS_UNMOCK(we_are_hibernating);
+    NS_UNMOCK(get_options);
+    NS_UNMOCK(public_server_mode);
+    NS_UNMOCK(router_get_my_routerinfo);
+    NS_UNMOCK(node_get_by_id);
+    NS_UNMOCK(logv);
+    NS_UNMOCK(server_mode);
+    tor_free(mock_routerinfo);
+}
+
+static double
+NS(tls_get_write_overhead_ratio)(void)
+{
+  return 1.0;
+}
+
+static int
+NS(we_are_hibernating)(void)
+{
+  return 0;
+}
+
+static const or_options_t *
+NS(get_options)(void)
+{
+  return NULL;
+}
+
+static int
+NS(public_server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 1;
+}
+
+static const routerinfo_t *
+NS(router_get_my_routerinfo)(void)
+{
+  mock_routerinfo = tor_malloc(sizeof(routerinfo_t));
+
+  return mock_routerinfo;
+}
+
+static const node_t *
+NS(node_get_by_id)(const char *identity_digest)
+{
+  (void)identity_digest;
+
+  return NULL;
+}
+
+static void
+NS(logv)(int severity, log_domain_mask_t domain,
+  const char *funcname, const char *suffix, const char *format, va_list ap)
+{
+  switch (CALLED(logv))
+  {
+    case 0:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Heartbeat: It seems like we are not in the cached consensus.");
+      break;
+    case 1:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Heartbeat: Tor's uptime is %s, with %d circuits open. "
+          "I've sent %s and received %s.%s");
+      tt_str_op(va_arg(ap, char *), ==, "0:00 hours");  /* uptime */
+      tt_int_op(va_arg(ap, int), ==, 0);  /* count_circuits() */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_sent */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_rcvd */
+      tt_str_op(va_arg(ap, char *), ==, "");  /* hibernating */
+      break;
+    case 2:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(
+          strstr(funcname, "rep_hist_log_circuit_handshake_stats"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+        "Circuit handshake stats since last time: %d/%d TAP, %d/%d NTor.");
+      tt_int_op(va_arg(ap, int), ==, 1);  /* handshakes assigned (TAP) */
+      tt_int_op(va_arg(ap, int), ==, 1);  /* handshakes requested (TAP) */
+      tt_int_op(va_arg(ap, int), ==, 1);  /* handshakes assigned (NTOR) */
+      tt_int_op(va_arg(ap, int), ==, 1);  /* handshakes requested (NTOR) */
+      break;
+    default:
+      tt_abort_msg("unexpected call to logv()");  // TODO: prettyprint args
+      break;
+  }
+
+  done:
+    CALLED(logv)++;
+}
+
+static int
+NS(server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE ASPECT(log_heartbeat, simple)
+
+/*
+ * Tests that log_heartbeat() correctly logs heartbeat information
+ * normally.
+ */
+
+NS_DECL(double, tls_get_write_overhead_ratio, (void));
+NS_DECL(int, we_are_hibernating, (void));
+NS_DECL(const or_options_t *, get_options, (void));
+NS_DECL(int, public_server_mode, (const or_options_t *options));
+NS_DECL(long, get_uptime, (void));
+NS_DECL(uint64_t, get_bytes_read, (void));
+NS_DECL(uint64_t, get_bytes_written, (void));
+NS_DECL(void, logv, (int severity, log_domain_mask_t domain,
+    const char *funcname, const char *suffix, const char *format, va_list ap));
+NS_DECL(int, server_mode, (const or_options_t *options));
+
+static void
+NS(test_main)(void *arg)
+{
+  int expected, actual;
+  (void)arg;
+
+  NS_MOCK(tls_get_write_overhead_ratio);
+  NS_MOCK(we_are_hibernating);
+  NS_MOCK(get_options);
+  NS_MOCK(public_server_mode);
+  NS_MOCK(get_uptime);
+  NS_MOCK(get_bytes_read);
+  NS_MOCK(get_bytes_written);
+  NS_MOCK(logv);
+  NS_MOCK(server_mode);
+
+  log_global_min_severity_ = LOG_DEBUG;
+
+  expected = 0;
+  actual = log_heartbeat(0);
+
+  tt_int_op(actual, ==, expected);
+
+  done:
+    NS_UNMOCK(tls_get_write_overhead_ratio);
+    NS_UNMOCK(we_are_hibernating);
+    NS_UNMOCK(get_options);
+    NS_UNMOCK(public_server_mode);
+    NS_UNMOCK(get_uptime);
+    NS_UNMOCK(get_bytes_read);
+    NS_UNMOCK(get_bytes_written);
+    NS_UNMOCK(logv);
+    NS_UNMOCK(server_mode);
+}
+
+static double
+NS(tls_get_write_overhead_ratio)(void)
+{
+  return 1.0;
+}
+
+static int
+NS(we_are_hibernating)(void)
+{
+  return 1;
+}
+
+static const or_options_t *
+NS(get_options)(void)
+{
+  return NULL;
+}
+
+static int
+NS(public_server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+static long
+NS(get_uptime)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_read)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_written)(void)
+{
+  return 0;
+}
+
+static void
+NS(logv)(int severity, log_domain_mask_t domain, const char *funcname,
+  const char *suffix, const char *format, va_list ap)
+{
+  tt_int_op(severity, ==, LOG_NOTICE);
+  tt_int_op(domain, ==, LD_HEARTBEAT);
+  tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+  tt_ptr_op(suffix, ==, NULL);
+  tt_str_op(format, ==,
+      "Heartbeat: Tor's uptime is %s, with %d circuits open. "
+      "I've sent %s and received %s.%s");
+  tt_str_op(va_arg(ap, char *), ==, "0:00 hours");  /* uptime */
+  tt_int_op(va_arg(ap, int), ==, 0);  /* count_circuits() */
+  tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_sent */
+  tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_rcvd */
+  tt_str_op(va_arg(ap, char *), ==, " We are currently hibernating.");
+
+  done:
+    ;
+}
+
+static int
+NS(server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE ASPECT(log_heartbeat, calls_log_accounting)
+
+/*
+ * Tests that log_heartbeat() correctly logs heartbeat information
+ * and accounting information when configured.
+ */
+
+NS_DECL(double, tls_get_write_overhead_ratio, (void));
+NS_DECL(int, we_are_hibernating, (void));
+NS_DECL(const or_options_t *, get_options, (void));
+NS_DECL(int, public_server_mode, (const or_options_t *options));
+NS_DECL(long, get_uptime, (void));
+NS_DECL(uint64_t, get_bytes_read, (void));
+NS_DECL(uint64_t, get_bytes_written, (void));
+NS_DECL(void, logv, (int severity, log_domain_mask_t domain,
+    const char *funcname, const char *suffix, const char *format, va_list ap));
+NS_DECL(int, server_mode, (const or_options_t *options));
+NS_DECL(or_state_t *, get_or_state, (void));
+NS_DECL(int, accounting_is_enabled, (const or_options_t *options));
+NS_DECL(time_t, accounting_get_end_time, (void));
+
+static void
+NS(test_main)(void *arg)
+{
+  int expected, actual;
+  (void)arg;
+
+  NS_MOCK(tls_get_write_overhead_ratio);
+  NS_MOCK(we_are_hibernating);
+  NS_MOCK(get_options);
+  NS_MOCK(public_server_mode);
+  NS_MOCK(get_uptime);
+  NS_MOCK(get_bytes_read);
+  NS_MOCK(get_bytes_written);
+  NS_MOCK(logv);
+  NS_MOCK(server_mode);
+  NS_MOCK(get_or_state);
+  NS_MOCK(accounting_is_enabled);
+  NS_MOCK(accounting_get_end_time);
+
+  log_global_min_severity_ = LOG_DEBUG;
+
+  expected = 0;
+  actual = log_heartbeat(0);
+
+  tt_int_op(actual, ==, expected);
+  tt_int_op(CALLED(logv), ==, 2);
+
+  done:
+    NS_UNMOCK(tls_get_write_overhead_ratio);
+    NS_UNMOCK(we_are_hibernating);
+    NS_UNMOCK(get_options);
+    NS_UNMOCK(public_server_mode);
+    NS_UNMOCK(get_uptime);
+    NS_UNMOCK(get_bytes_read);
+    NS_UNMOCK(get_bytes_written);
+    NS_UNMOCK(logv);
+    NS_UNMOCK(server_mode);
+    NS_UNMOCK(accounting_is_enabled);
+    NS_UNMOCK(accounting_get_end_time);
+}
+
+static double
+NS(tls_get_write_overhead_ratio)(void)
+{
+  return 1.0;
+}
+
+static int
+NS(we_are_hibernating)(void)
+{
+  return 0;
+}
+
+static const or_options_t *
+NS(get_options)(void)
+{
+  or_options_t *mock_options = tor_malloc_zero(sizeof(or_options_t));
+  mock_options->AccountingMax = 0;
+
+  return mock_options;
+}
+
+static int
+NS(public_server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+static long
+NS(get_uptime)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_read)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_written)(void)
+{
+  return 0;
+}
+
+static void
+NS(logv)(int severity, log_domain_mask_t domain,
+  const char *funcname, const char *suffix, const char *format, va_list ap)
+{
+  switch (CALLED(logv))
+  {
+    case 0:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Heartbeat: Tor's uptime is %s, with %d circuits open. "
+          "I've sent %s and received %s.%s");
+      tt_str_op(va_arg(ap, char *), ==, "0:00 hours");  /* uptime */
+      tt_int_op(va_arg(ap, int), ==, 0);  /* count_circuits() */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_sent */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_rcvd */
+      tt_str_op(va_arg(ap, char *), ==, "");  /* hibernating */
+      break;
+    case 1:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_accounting"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Heartbeat: Accounting enabled. Sent: %s / %s, Received: %s / %s. "
+          "The current accounting interval ends on %s, in %s.");
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* acc_sent */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* acc_max */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* acc_rcvd */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* acc_max */
+      /* format_local_iso_time uses local tz, just check mins and secs. */
+      tt_ptr_op(strstr(va_arg(ap, char *), ":01:00"), !=, NULL);  /* end_buf */
+      tt_str_op(va_arg(ap, char *), ==, "0:01 hours");   /* remaining */
+      break;
+    default:
+      tt_abort_msg("unexpected call to logv()");  // TODO: prettyprint args
+      break;
+  }
+
+  done:
+    CALLED(logv)++;
+}
+
+static int
+NS(server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 1;
+}
+
+static int
+NS(accounting_is_enabled)(const or_options_t *options)
+{
+  (void)options;
+
+  return 1;
+}
+
+static time_t
+NS(accounting_get_end_time)(void)
+{
+  return 60;
+}
+
+static or_state_t *
+NS(get_or_state)(void)
+{
+  or_state_t *mock_state = tor_malloc_zero(sizeof(or_state_t));
+  mock_state->AccountingBytesReadInInterval = 0;
+  mock_state->AccountingBytesWrittenInInterval = 0;
+
+  return mock_state;
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE ASPECT(log_heartbeat, packaged_cell_fullness)
+
+/*
+ * Tests that log_heartbeat() correctly logs packaged cell
+ * fullness information.
+ */
+
+NS_DECL(double, tls_get_write_overhead_ratio, (void));
+NS_DECL(int, we_are_hibernating, (void));
+NS_DECL(const or_options_t *, get_options, (void));
+NS_DECL(int, public_server_mode, (const or_options_t *options));
+NS_DECL(long, get_uptime, (void));
+NS_DECL(uint64_t, get_bytes_read, (void));
+NS_DECL(uint64_t, get_bytes_written, (void));
+NS_DECL(void, logv, (int severity, log_domain_mask_t domain,
+    const char *funcname, const char *suffix, const char *format, va_list ap));
+NS_DECL(int, server_mode, (const or_options_t *options));
+NS_DECL(int, accounting_is_enabled, (const or_options_t *options));
+
+static void
+NS(test_main)(void *arg)
+{
+  int expected, actual;
+  (void)arg;
+
+  NS_MOCK(tls_get_write_overhead_ratio);
+  NS_MOCK(we_are_hibernating);
+  NS_MOCK(get_options);
+  NS_MOCK(public_server_mode);
+  NS_MOCK(get_uptime);
+  NS_MOCK(get_bytes_read);
+  NS_MOCK(get_bytes_written);
+  NS_MOCK(logv);
+  NS_MOCK(server_mode);
+  NS_MOCK(accounting_is_enabled);
+  log_global_min_severity_ = LOG_DEBUG;
+
+  stats_n_data_bytes_packaged = RELAY_PAYLOAD_SIZE;
+  stats_n_data_cells_packaged = 1;
+  expected = 0;
+  actual = log_heartbeat(0);
+
+  tt_int_op(actual, ==, expected);
+  tt_int_op(CALLED(logv), ==, 2);
+
+  done:
+    stats_n_data_bytes_packaged = 0;
+    stats_n_data_cells_packaged = 0;
+    NS_UNMOCK(tls_get_write_overhead_ratio);
+    NS_UNMOCK(we_are_hibernating);
+    NS_UNMOCK(get_options);
+    NS_UNMOCK(public_server_mode);
+    NS_UNMOCK(get_uptime);
+    NS_UNMOCK(get_bytes_read);
+    NS_UNMOCK(get_bytes_written);
+    NS_UNMOCK(logv);
+    NS_UNMOCK(server_mode);
+    NS_UNMOCK(accounting_is_enabled);
+}
+
+static double
+NS(tls_get_write_overhead_ratio)(void)
+{
+  return 1.0;
+}
+
+static int
+NS(we_are_hibernating)(void)
+{
+  return 0;
+}
+
+static const or_options_t *
+NS(get_options)(void)
+{
+  return NULL;
+}
+
+static int
+NS(public_server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+static long
+NS(get_uptime)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_read)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_written)(void)
+{
+  return 0;
+}
+
+static void
+NS(logv)(int severity, log_domain_mask_t domain, const char *funcname,
+    const char *suffix, const char *format, va_list ap)
+{
+  switch (CALLED(logv))
+  {
+    case 0:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Heartbeat: Tor's uptime is %s, with %d circuits open. "
+          "I've sent %s and received %s.%s");
+      tt_str_op(va_arg(ap, char *), ==, "0:00 hours");  /* uptime */
+      tt_int_op(va_arg(ap, int), ==, 0);  /* count_circuits() */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_sent */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_rcvd */
+      tt_str_op(va_arg(ap, char *), ==, "");  /* hibernating */
+      break;
+    case 1:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Average packaged cell fullness: %2.3f%%");
+      tt_int_op(fabs(va_arg(ap, double) - 100.0) <= DBL_EPSILON, ==, 1);
+      break;
+    default:
+      tt_abort_msg("unexpected call to logv()");  // TODO: prettyprint args
+      break;
+  }
+
+  done:
+    CALLED(logv)++;
+}
+
+static int
+NS(server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+static int
+NS(accounting_is_enabled)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+#undef NS_SUBMODULE
+#define NS_SUBMODULE ASPECT(log_heartbeat, tls_write_overhead)
+
+/*
+ * Tests that log_heartbeat() correctly logs the TLS write overhead information
+ * when the TLS write overhead ratio exceeds 1.
+ */
+
+NS_DECL(double, tls_get_write_overhead_ratio, (void));
+NS_DECL(int, we_are_hibernating, (void));
+NS_DECL(const or_options_t *, get_options, (void));
+NS_DECL(int, public_server_mode, (const or_options_t *options));
+NS_DECL(long, get_uptime, (void));
+NS_DECL(uint64_t, get_bytes_read, (void));
+NS_DECL(uint64_t, get_bytes_written, (void));
+NS_DECL(void, logv, (int severity, log_domain_mask_t domain,
+    const char *funcname, const char *suffix, const char *format, va_list ap));
+NS_DECL(int, server_mode, (const or_options_t *options));
+NS_DECL(int, accounting_is_enabled, (const or_options_t *options));
+
+static void
+NS(test_main)(void *arg)
+{
+  int expected, actual;
+  (void)arg;
+
+  NS_MOCK(tls_get_write_overhead_ratio);
+  NS_MOCK(we_are_hibernating);
+  NS_MOCK(get_options);
+  NS_MOCK(public_server_mode);
+  NS_MOCK(get_uptime);
+  NS_MOCK(get_bytes_read);
+  NS_MOCK(get_bytes_written);
+  NS_MOCK(logv);
+  NS_MOCK(server_mode);
+  NS_MOCK(accounting_is_enabled);
+  stats_n_data_cells_packaged = 0;
+  log_global_min_severity_ = LOG_DEBUG;
+
+  expected = 0;
+  actual = log_heartbeat(0);
+
+  tt_int_op(actual, ==, expected);
+  tt_int_op(CALLED(logv), ==, 2);
+
+  done:
+    NS_UNMOCK(tls_get_write_overhead_ratio);
+    NS_UNMOCK(we_are_hibernating);
+    NS_UNMOCK(get_options);
+    NS_UNMOCK(public_server_mode);
+    NS_UNMOCK(get_uptime);
+    NS_UNMOCK(get_bytes_read);
+    NS_UNMOCK(get_bytes_written);
+    NS_UNMOCK(logv);
+    NS_UNMOCK(server_mode);
+    NS_UNMOCK(accounting_is_enabled);
+}
+
+static double
+NS(tls_get_write_overhead_ratio)(void)
+{
+  return 2.0;
+}
+
+static int
+NS(we_are_hibernating)(void)
+{
+  return 0;
+}
+
+static const or_options_t *
+NS(get_options)(void)
+{
+  return NULL;
+}
+
+static int
+NS(public_server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+static long
+NS(get_uptime)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_read)(void)
+{
+  return 0;
+}
+
+static uint64_t
+NS(get_bytes_written)(void)
+{
+  return 0;
+}
+
+static void
+NS(logv)(int severity, log_domain_mask_t domain,
+  const char *funcname, const char *suffix, const char *format, va_list ap)
+{
+  switch (CALLED(logv))
+  {
+    case 0:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==,
+          "Heartbeat: Tor's uptime is %s, with %d circuits open. "
+          "I've sent %s and received %s.%s");
+      tt_str_op(va_arg(ap, char *), ==, "0:00 hours");  /* uptime */
+      tt_int_op(va_arg(ap, int), ==, 0);  /* count_circuits() */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_sent */
+      tt_str_op(va_arg(ap, char *), ==, "0 kB");  /* bw_rcvd */
+      tt_str_op(va_arg(ap, char *), ==, "");  /* hibernating */
+      break;
+    case 1:
+      tt_int_op(severity, ==, LOG_NOTICE);
+      tt_int_op(domain, ==, LD_HEARTBEAT);
+      tt_ptr_op(strstr(funcname, "log_heartbeat"), !=, NULL);
+      tt_ptr_op(suffix, ==, NULL);
+      tt_str_op(format, ==, "TLS write overhead: %.f%%");
+      tt_int_op(fabs(va_arg(ap, double) - 100.0) <= DBL_EPSILON, ==, 1);
+      break;
+    default:
+      tt_abort_msg("unexpected call to logv()");  // TODO: prettyprint args
+      break;
+  }
+
+  done:
+    CALLED(logv)++;
+}
+
+static int
+NS(server_mode)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+static int
+NS(accounting_is_enabled)(const or_options_t *options)
+{
+  (void)options;
+
+  return 0;
+}
+
+#undef NS_SUBMODULE
+
+struct testcase_t status_tests[] = {
+  TEST_CASE(count_circuits),
+  TEST_CASE(secs_to_uptime),
+  TEST_CASE(bytes_to_usage),
+  TEST_CASE_ASPECT(log_heartbeat, fails),
+  TEST_CASE_ASPECT(log_heartbeat, simple),
+  TEST_CASE_ASPECT(log_heartbeat, not_in_consensus),
+  TEST_CASE_ASPECT(log_heartbeat, calls_log_accounting),
+  TEST_CASE_ASPECT(log_heartbeat, packaged_cell_fullness),
+  TEST_CASE_ASPECT(log_heartbeat, tls_write_overhead),
+  END_OF_TESTCASES
+};
+