diff --git a/ChangeLog b/ChangeLog index f2ca5a7b3..a4faef25f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,14 @@ Darshan Release Change Log Darshan-3.4.1-pre1 ============= * Deprecated --file-list and --file-list-detailed options in darshan-parser +* Added "darshan_accumulator" API to the logutils library + - _create(), _inject(), _emit(), and _destroy() + - generalizes the mechanism for producing summation records and derived + metrics for sets of records from a given module + - refactored darshan-parser to use new API + - implemented support for accumulators in POSIX, STDIO, and MPIIO modules +* Integrated the µnit Testing Framework in darshan-util + - implemented unit tests for darshan_accumlator API Darshan-3.4.0 ============= diff --git a/darshan-util/Makefile.am b/darshan-util/Makefile.am index 4c983ea0f..2a28e50ae 100644 --- a/darshan-util/Makefile.am +++ b/darshan-util/Makefile.am @@ -21,7 +21,8 @@ libdarshan_util_la_SOURCES = darshan-null-logutils.c \ darshan-stdio-logutils.c \ darshan-dxt-logutils.c \ darshan-heatmap-logutils.c \ - darshan-mdhim-logutils.c + darshan-mdhim-logutils.c \ + darshan-logutils-accumulator.c include_HEADERS = darshan-null-logutils.h \ darshan-logutils.h \ @@ -139,3 +140,14 @@ install-exec-hook: endif .PHONY: clean-local-check + +TESTS = +XFAIL_TESTS = +check_PROGRAMS = +noinst_HEADERS = + +include $(top_srcdir)/tests/unit-tests/Makefile.subdir + +# convenience rule for building test programs +.phony: tests +tests: $(check_PROGRAMS) diff --git a/darshan-util/configure.ac b/darshan-util/configure.ac index 5eb11efd0..2e2b4ff1f 100644 --- a/darshan-util/configure.ac +++ b/darshan-util/configure.ac @@ -18,7 +18,7 @@ AC_CONFIG_SRCDIR([darshan-logutils.h]) AC_CONFIG_AUX_DIR(../maint/scripts) AC_CONFIG_MACRO_DIRS(../maint/config) -AM_INIT_AUTOMAKE([1.13 foreign tar-pax]) +AM_INIT_AUTOMAKE([1.13 foreign tar-pax subdir-objects]) AM_SILENT_RULES([yes]) AM_MAINTAINER_MODE([enable]) diff --git a/darshan-util/darshan-logutils-accumulator.c b/darshan-util/darshan-logutils-accumulator.c new file mode 100644 index 000000000..b4f091212 --- /dev/null +++ b/darshan-util/darshan-logutils-accumulator.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2022 University of Chicago. + * See COPYRIGHT notice in top-level directory. + * + */ + +/* This function implements the accumulator API (darshan_accumlator*) + * functions in darshan-logutils.h. + */ + +#include +#include + +#include "darshan-logutils.h" +#include "uthash-1.9.2/src/uthash.h" + +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +/* struct to track per-file metrics */ +typedef struct file_hash_entry_s +{ + UT_hash_handle hlink; + darshan_record_id rec_id; + int64_t r_bytes; /* bytes read */ + int64_t w_bytes; /* bytes written */ + int64_t max_offset; /* maximum offset accessed */ + int64_t nprocs; /* nprocs that accessed it */ +} file_hash_entry_t; + +/* accumulator state */ +struct darshan_accumulator_st { + darshan_module_id module_id; + int64_t job_nprocs; + void* agg_record; + int num_records; + file_hash_entry_t *file_hash_table; + + /* amount of time consumed by slowest rank in shared files, across all + * shared files observed + */ + double shared_io_total_time_by_slowest; + /* how many total bytes were read or written? */ + int64_t total_bytes; + /* for non-shared files, how long did each rank spend in IO? */ + double *rank_cumul_io_total_time; + double *rank_cumul_rw_only_time; + double *rank_cumul_md_only_time; +}; + +int darshan_accumulator_create(darshan_module_id id, + int64_t job_nprocs, + darshan_accumulator* new_accumulator) +{ + *new_accumulator = NULL; + + if(id >= DARSHAN_KNOWN_MODULE_COUNT) + return(-1); + + if(!mod_logutils[id]->log_agg_records || + !mod_logutils[id]->log_sizeof_record || + !mod_logutils[id]->log_record_metrics) { + /* this module doesn't support this operation */ + return(-1); + } + + *new_accumulator = calloc(1, sizeof(struct darshan_accumulator_st)); + if(!(*new_accumulator)) + return(-1); + + (*new_accumulator)->module_id = id; + (*new_accumulator)->job_nprocs = job_nprocs; + (*new_accumulator)->agg_record = calloc(1, DEF_MOD_BUF_SIZE); + if(!(*new_accumulator)->agg_record) { + free(*new_accumulator); + *new_accumulator = NULL; + return(-1); + } + /* 3 arrays handled in one malloc */ + (*new_accumulator)->rank_cumul_io_total_time = calloc(job_nprocs*3, sizeof(double)); + if(!(*new_accumulator)->rank_cumul_io_total_time) { + free((*new_accumulator)->agg_record); + free(*new_accumulator); + *new_accumulator = NULL; + return(-1); + } + (*new_accumulator)->rank_cumul_rw_only_time = &((*new_accumulator)->rank_cumul_io_total_time[job_nprocs]); + (*new_accumulator)->rank_cumul_md_only_time = &((*new_accumulator)->rank_cumul_io_total_time[job_nprocs*2]); + + return(0); +} + +int darshan_accumulator_inject(darshan_accumulator acc, + void* record_array, + int record_count) +{ + int i; + void* new_record = record_array; + uint64_t rec_id; + int64_t r_bytes; + int64_t w_bytes; + int64_t max_offset; + int64_t nprocs; + int64_t rank; + double io_total_time; + double md_only_time; + double rw_only_time; + int ret; + file_hash_entry_t *hfile = NULL; + + if(!mod_logutils[acc->module_id]->log_agg_records || + !mod_logutils[acc->module_id]->log_sizeof_record || + !mod_logutils[acc->module_id]->log_record_metrics) { + /* this module doesn't support this operation */ + return(-1); + } + + for(i=0; inum_records == 0) + mod_logutils[acc->module_id]->log_agg_records(new_record, acc->agg_record, 1); + else + mod_logutils[acc->module_id]->log_agg_records(new_record, acc->agg_record, 0); + acc->num_records++; + + /* retrieve generic metrics from record */ + ret = mod_logutils[acc->module_id]->log_record_metrics( new_record, + &rec_id, &r_bytes, &w_bytes, &max_offset, &io_total_time, + &md_only_time, &rw_only_time, &rank, &nprocs); + if(ret < 0) + return(-1); + + /* accumulate performance metrics */ + + /* total bytes moved */ + acc->total_bytes += (r_bytes + w_bytes); + + if(rank < 0) { + /* sum the slowest I/O time across all shared files */ + acc->shared_io_total_time_by_slowest += io_total_time; + } + else { + /* sum per-rank I/O times (including meta and rw breakdown) for + * each rank separately + */ + assert(rank < acc->job_nprocs); + acc->rank_cumul_io_total_time[rank] += io_total_time; + acc->rank_cumul_rw_only_time[rank] += rw_only_time; + acc->rank_cumul_md_only_time[rank] += md_only_time; + } + + /* track in hash table for per-file metrics; there may be multiple + * records that refer to the same file */ + HASH_FIND(hlink, acc->file_hash_table, &rec_id, sizeof(rec_id), hfile); + if(!hfile) { + /* first time we've seen this file in this accumulator */ + hfile = calloc(1, sizeof(*hfile)); + if(!hfile) { + return(-1); + } + + /* add to hash table */ + hfile->rec_id = rec_id; + HASH_ADD(hlink, acc->file_hash_table, rec_id, sizeof(rec_id), hfile); + } + + /* we have hfile at this point (either existing or newly created); + * increment metrics + */ + hfile->r_bytes += r_bytes; + hfile->w_bytes += w_bytes; + if(max_offset == -1) + hfile->max_offset = -1; /* this module doesn't support this */ + else + hfile->max_offset = max(hfile->max_offset, max_offset); + if (nprocs == -1) + hfile->nprocs = -1; /* globally shared */ + else + hfile->nprocs += nprocs; /* partially shared or unique, as far as we + know so far */ + + /* advance to next record */ + new_record += mod_logutils[acc->module_id]->log_sizeof_record(new_record); + } + + return(0); +} + +/* NOTE: use -1 for procs to indicate that the file was globally shared. + * This will be marked in the category counters if we find a file hash that + * was globally shared or if the proc value gets incremented to cover all + * processes in the job. + */ +#define CATEGORY_INC(__cat_counters_p, __fhe_p, __job_nprocs) \ +do{\ + if(!(__cat_counters_p)) \ + break; \ + __cat_counters_p->count++; \ + __cat_counters_p->total_read_volume_bytes += __fhe_p->r_bytes; \ + __cat_counters_p->total_write_volume_bytes += __fhe_p->w_bytes; \ + __cat_counters_p->max_read_volume_bytes = \ + max(__cat_counters_p->max_read_volume_bytes, __fhe_p->r_bytes); \ + __cat_counters_p->max_write_volume_bytes = \ + max(__cat_counters_p->max_write_volume_bytes, __fhe_p->w_bytes); \ + if(__fhe_p->max_offset == -1) {\ + __cat_counters_p->total_max_offset_bytes = -1; \ + __cat_counters_p->max_offset_bytes = -1; \ + }\ + else {\ + __cat_counters_p->total_max_offset_bytes += __fhe_p->max_offset; \ + __cat_counters_p->max_offset_bytes = \ + max(__cat_counters_p->max_offset_bytes, __fhe_p->max_offset); \ + }\ + if(__fhe_p->nprocs > 0 && __cat_counters_p->nprocs > -1) \ + __cat_counters_p->nprocs += __fhe_p->nprocs; \ + if(__fhe_p->nprocs < 0 || __cat_counters_p->nprocs >= __job_nprocs) \ + __cat_counters_p->nprocs = -1; \ +}while(0) + +int darshan_accumulator_emit(darshan_accumulator acc, + struct darshan_derived_metrics* metrics, + void* summation_record) +{ + file_hash_entry_t *curr = NULL; + file_hash_entry_t *tmp_file = NULL; + struct darshan_file_category_counters* cat_counters; + int64_t i; + + memset(metrics, 0, sizeof(*metrics)); + + /* walk hash table to construct metrics by file category */ + HASH_ITER(hlink, acc->file_hash_table, curr, tmp_file) + { + /* all files */ + cat_counters = &metrics->category_counters[DARSHAN_ALL_FILES]; + CATEGORY_INC(cat_counters, curr, acc->job_nprocs); + + /* read-only, write-only, and read-write */ + if(curr->r_bytes > 0 && curr->w_bytes == 0) + cat_counters = &metrics->category_counters[DARSHAN_RO_FILES]; + else if(curr->w_bytes > 0 && curr->r_bytes == 0) + cat_counters = &metrics->category_counters[DARSHAN_WO_FILES]; + else if(curr->w_bytes > 0 && curr->r_bytes > 0) + cat_counters = &metrics->category_counters[DARSHAN_RW_FILES]; + else + cat_counters = NULL; + CATEGORY_INC(cat_counters, curr, acc->job_nprocs); + + /* unique, shared, and partially shared */ + if(curr->nprocs == 1) + cat_counters = &metrics->category_counters[DARSHAN_UNIQ_FILES]; + else if(curr->nprocs == -1) + cat_counters = &metrics->category_counters[DARSHAN_SHARED_FILES]; + else + cat_counters = &metrics->category_counters[DARSHAN_PART_SHARED_FILES]; + CATEGORY_INC(cat_counters, curr, acc->job_nprocs); + } + + /* copy out aggregate record we have been accumulating so far */ + memcpy(summation_record, acc->agg_record, mod_logutils[acc->module_id]->log_sizeof_record(acc->agg_record)); + + /* calculate derived performance metrics */ + metrics->total_bytes = acc->total_bytes; + metrics->shared_io_total_time_by_slowest + = acc->shared_io_total_time_by_slowest; + /* determine which rank had the slowest path through unique files */ + for (i = 0; i < acc->job_nprocs; i++) { + if (acc->rank_cumul_io_total_time[i] + > metrics->unique_io_total_time_by_slowest) { + metrics->unique_io_total_time_by_slowest + = acc->rank_cumul_io_total_time[i]; + metrics->unique_rw_only_time_by_slowest + = acc->rank_cumul_rw_only_time[i]; + metrics->unique_md_only_time_by_slowest + = acc->rank_cumul_md_only_time[i]; + metrics->unique_io_slowest_rank = i; + } + } + + /* aggregate io time is estimated as the time consumed by the slowest + * rank in unique files plus the time consumed by the slowest rank in in + * each shared file + */ + metrics->agg_time_by_slowest = metrics->unique_io_total_time_by_slowest + + metrics->shared_io_total_time_by_slowest; + /* aggregate rate is total bytes deviced by above; guard against divide + * by zero calculation, though + */ + if (metrics->agg_time_by_slowest) + metrics->agg_perf_by_slowest + = ((double)metrics->total_bytes / 1048576.0) + / metrics->agg_time_by_slowest; + + return(0); +} + +int darshan_accumulator_destroy(darshan_accumulator acc) +{ + file_hash_entry_t *curr = NULL; + file_hash_entry_t *tmp_file = NULL; + + if(!acc) + return(0); + + /* three arrays, but handled by one malloc (see _create()) */ + if(acc->rank_cumul_io_total_time) + free(acc->rank_cumul_io_total_time); + + if(acc->agg_record) + free(acc->agg_record); + + /* walk file hash table, freeing memory as we go */ + HASH_ITER(hlink, acc->file_hash_table, curr, tmp_file) + { + HASH_DELETE(hlink, acc->file_hash_table, curr); + free(curr); + } + + free(acc); + + return(0); +} diff --git a/darshan-util/darshan-logutils.h b/darshan-util/darshan-logutils.h index 51aa1b375..c4bcab5ce 100644 --- a/darshan-util/darshan-logutils.h +++ b/darshan-util/darshan-logutils.h @@ -148,6 +148,23 @@ struct darshan_mod_logutil_funcs void *agg_rec, int init_flag ); + /* report the true size of the record, including variable-length data if + * present + */ + int (*log_sizeof_record)( + void *rec); + /* extract some generic metrics from module record type */ + int (*log_record_metrics)( + void* rec, /* input record */ + uint64_t* rec_id, /* record id */ + int64_t* r_bytes, /* bytes read */ + int64_t* w_bytes, /* bytes written */ + int64_t* max_offset, /* maximum offset accessed */ + double* io_total_time, /* total time spent in all io fns */ + double* md_only_time, /* time spent in metadata fns, if known */ + double* rw_only_time, /* time spent in read/write fns, if known */ + int64_t* rank, /* rank associated with record (-1 for shared) */ + int64_t* nprocs); /* nprocs that accessed it */ }; extern struct darshan_mod_logutil_funcs *mod_logutils[]; @@ -273,4 +290,102 @@ int darshan_log_get_record(darshan_fd fd, int mod_idx, void **buf); memcpy(__ptr, __dst_char, 4); \ } while(0) +/***************************************************************** + * The functions in this section make up the accumulator API, which is a + * mechanism for aggregating records to produce derived metrics and + * summaries. + */ + +/* opaque accumulator reference */ +struct darshan_accumulator_st; +typedef struct darshan_accumulator_st* darshan_accumulator; + +/* Instantiate a stateful accumulator for a particular module type. + */ +int darshan_accumulator_create(darshan_module_id id, + int64_t job_nprocs, + darshan_accumulator* new_accumulator); + +/* Add a record to the accumulator. The record is an untyped void* (size + * implied by record type) following the convention of other logutils + * functions. Multiple records may be injected at once by setting + * record_count > 1 and packing records into a contiguous memory region. + */ +int darshan_accumulator_inject(darshan_accumulator accumulator, + void* record_array, + int record_count); + +struct darshan_file_category_counters { + int64_t count; /* number of files in this category */ + int64_t total_read_volume_bytes; /* total read traffic volume */ + int64_t total_write_volume_bytes;/* total write traffic volume */ + int64_t max_read_volume_bytes; /* maximum read traffic volume to 1 file */ + int64_t max_write_volume_bytes; /* maximum write traffic volume to 1 file */ + int64_t total_max_offset_bytes; /* summation of max_offsets */ + int64_t max_offset_bytes; /* largest max_offset */ + int64_t nprocs; /* how many procs accessed (-1 for "all") */ +}; + +enum darshan_file_category { + DARSHAN_ALL_FILES = 0, + DARSHAN_RO_FILES, + DARSHAN_WO_FILES, + DARSHAN_RW_FILES, + DARSHAN_UNIQ_FILES, + DARSHAN_SHARED_FILES, + DARSHAN_PART_SHARED_FILES, + DARSHAN_FILE_CATEGORY_MAX +}; + +/* aggregate metrics that can be derived from an accumulator. If a given + * record time doesn't support a particular field, then it will be set to -1 + * (for int64_t values) or NAN (for double values). + */ +struct darshan_derived_metrics { + /* total bytes moved (read and write) */ + int64_t total_bytes; + + /* combined meta and rw time spent in unique files by slowest rank */ + double unique_io_total_time_by_slowest; + /* rw time spent in unique files by slowest rank */ + double unique_rw_only_time_by_slowest; + /* meta time spent in unique files by slowest rank */ + double unique_md_only_time_by_slowest; + /* which rank was the slowest for unique files */ + int unique_io_slowest_rank; + + /* combined meta and rw time speint by slowest rank on shared file */ + /* Note that (unlike the unique file counters above) we cannot + * discriminate md and rw time separately within shared files. + */ + double shared_io_total_time_by_slowest; + + /* overall throughput, accounting for the slowest path through both + * shared files and unique files + */ + double agg_perf_by_slowest; + /* overall elapsed io time, accounting for the slowest path through both + * shared files and unique files + */ + double agg_time_by_slowest; + + /* array of derived metrics broken down by different categories */ + struct darshan_file_category_counters + category_counters[DARSHAN_FILE_CATEGORY_MAX]; +}; + +/* Emit derived metrics _and_ a combined aggregate record from an accumulator. + * The aggregation_record uses the same format as the normal records for the + * module, but values are set to reflect summations across all accumulated + * records. + */ +int darshan_accumulator_emit(darshan_accumulator accumulator, + struct darshan_derived_metrics* metrics, + void* aggregation_record); + +/* frees resources associated with an accumulator */ +int darshan_accumulator_destroy(darshan_accumulator accumulator); + +/*****************************************************************/ + #endif diff --git a/darshan-util/darshan-mpiio-logutils.c b/darshan-util/darshan-mpiio-logutils.c index 0611b0e1b..9359750ae 100644 --- a/darshan-util/darshan-mpiio-logutils.c +++ b/darshan-util/darshan-mpiio-logutils.c @@ -22,6 +22,8 @@ #include "darshan-logutils.h" +#define max(a,b) (((a) > (b)) ? (a) : (b)) + /* counter name strings for the MPI-IO module */ #define X(a) #a, char *mpiio_counter_names[] = { @@ -43,6 +45,17 @@ static void darshan_log_print_mpiio_description(int ver); static void darshan_log_print_mpiio_file_diff(void *file_rec1, char *file_name1, void *file_rec2, char *file_name2); static void darshan_log_agg_mpiio_files(void *rec, void *agg_rec, int init_flag); +static int darshan_log_sizeof_mpiio_file(void* mpiio_buf_p); +static int darshan_log_record_metrics_mpiio_file(void* mpiio_buf_p, + uint64_t* rec_id, + int64_t* r_bytes, + int64_t* w_bytes, + int64_t* max_offset, + double* io_total_time, + double* md_only_time, + double* rw_only_time, + int64_t* rank, + int64_t* nprocs); struct darshan_mod_logutil_funcs mpiio_logutils = { @@ -51,9 +64,70 @@ struct darshan_mod_logutil_funcs mpiio_logutils = .log_print_record = &darshan_log_print_mpiio_file, .log_print_description = &darshan_log_print_mpiio_description, .log_print_diff = &darshan_log_print_mpiio_file_diff, - .log_agg_records = &darshan_log_agg_mpiio_files + .log_agg_records = &darshan_log_agg_mpiio_files, + .log_sizeof_record = &darshan_log_sizeof_mpiio_file, + .log_record_metrics = &darshan_log_record_metrics_mpiio_file }; +static int darshan_log_sizeof_mpiio_file(void* mpiio_buf_p) +{ + /* mpiio records have a fixed size */ + return(sizeof(struct darshan_mpiio_file)); +} + +static int darshan_log_record_metrics_mpiio_file(void* mpiio_buf_p, + uint64_t* rec_id, + int64_t* r_bytes, + int64_t* w_bytes, + int64_t* max_offset, + double* io_total_time, + double* md_only_time, + double* rw_only_time, + int64_t* rank, + int64_t* nprocs) +{ + struct darshan_mpiio_file *mpiio_rec = (struct darshan_mpiio_file *)mpiio_buf_p; + + *rec_id = mpiio_rec->base_rec.id; + *r_bytes = mpiio_rec->counters[MPIIO_BYTES_READ]; + *w_bytes = mpiio_rec->counters[MPIIO_BYTES_WRITTEN]; + + /* the mpiio module doesn't report this */ + *max_offset = -1; + + *rank = mpiio_rec->base_rec.rank; + /* nprocs is 1 per record, unless rank is negative, in which case we + * report -1 as the rank value to represent "all" + */ + if(mpiio_rec->base_rec.rank < 0) + *nprocs = -1; + else + *nprocs = 1; + + if(mpiio_rec->base_rec.rank < 0) { + /* shared file records populate a counter with the slowest rank time + * (derived during reduction). They do not have a breakdown of meta + * and rw time, though. + */ + *io_total_time = mpiio_rec->fcounters[MPIIO_F_SLOWEST_RANK_TIME]; + *md_only_time = 0; + *rw_only_time = 0; + } + else { + /* non-shared records have separate meta, read, and write values + * that we can combine as needed + */ + *io_total_time = mpiio_rec->fcounters[MPIIO_F_META_TIME] + + mpiio_rec->fcounters[MPIIO_F_READ_TIME] + + mpiio_rec->fcounters[MPIIO_F_WRITE_TIME]; + *md_only_time = mpiio_rec->fcounters[MPIIO_F_META_TIME]; + *rw_only_time = mpiio_rec->fcounters[MPIIO_F_READ_TIME] + + mpiio_rec->fcounters[MPIIO_F_WRITE_TIME]; + } + + return(0); +} + static int darshan_log_get_mpiio_file(darshan_fd fd, void** mpiio_buf_p) { struct darshan_mpiio_file *file = *((struct darshan_mpiio_file **)mpiio_buf_p); @@ -319,6 +393,7 @@ static void darshan_log_agg_mpiio_files(void *rec, void *agg_rec, int init_flag) int total_count; int64_t tmp_val[4]; int64_t tmp_cnt[4]; + int duplicate_mask[4] = {0}; int tmp_ndx; int shared_file_flag = 0; int64_t mpi_fastest_rank, mpi_slowest_rank, @@ -458,7 +533,8 @@ static void darshan_log_agg_mpiio_files(void *rec, void *agg_rec, int init_flag) if(agg_mpi_rec->counters[i + k] == mpi_rec->counters[j]) { agg_mpi_rec->counters[i + k + 4] += mpi_rec->counters[j + 4]; - mpi_rec->counters[j] = mpi_rec->counters[j + 4] = 0; + /* flag that we should ignore this one now */ + duplicate_mask[j-i] = 1; } } } @@ -466,6 +542,9 @@ static void darshan_log_agg_mpiio_files(void *rec, void *agg_rec, int init_flag) /* second, add new counters */ for(j = i; j < i + 4; j++) { + /* skip any that were handled above already */ + if(duplicate_mask[j-i]) + continue; tmp_ndx = 0; memset(tmp_val, 0, 4 * sizeof(int64_t)); memset(tmp_cnt, 0, 4 * sizeof(int64_t)); diff --git a/darshan-util/darshan-parser.c b/darshan-util/darshan-parser.c index 016970e9c..f2c847dbf 100644 --- a/darshan-util/darshan-parser.c +++ b/darshan-util/darshan-parser.c @@ -45,92 +45,13 @@ #define max(a,b) (((a) > (b)) ? (a) : (b)) -/* - * Datatypes - */ - -/* Structure to accumulate per-file derived metrics, regardless of how many - * ranks access it. The "file_hash_table" UT hash table data structure is - * used to keep track of all files that have been encountered in the log. - * The *_accum_file() functions are used to iteratively accumulate metrics, - * while the *_file_list() functions are used to emit them to stdout. - */ -typedef struct hash_entry_s -{ - UT_hash_handle hlink; - darshan_record_id rec_id; - int64_t type; - int64_t procs; - void *rec_dat; -} hash_entry_t; - -/* Structure to accumulate aggregate derived metrics across all files. This - * is usually caculated all at once (see *_calc_file() functions) after the - * file_hash_table has been fully populated with all files, and results are - * emitted to stdout in main(). - */ -typedef struct file_data_s -{ - int64_t total; - int64_t total_size; - int64_t total_max; - int64_t read_only; - int64_t read_only_size; - int64_t read_only_max; - int64_t write_only; - int64_t write_only_size; - int64_t write_only_max; - int64_t read_write; - int64_t read_write_size; - int64_t read_write_max; - int64_t unique; - int64_t unique_size; - int64_t unique_max; - int64_t shared; - int64_t shared_size; - int64_t shared_max; -} file_data_t; - -/* Structure to accumulate aggreate derived performance metrics across all - * files. Metrics are iteratively accumulated with accum_perf() and then - * finalized with calc_perf(). Final values are emitted to stdout in - * main(). - */ -typedef struct perf_data_s -{ - int64_t total_bytes; - double slowest_rank_io_total_time; - double slowest_rank_rw_only_time; - double slowest_rank_meta_only_time; - int slowest_rank_rank; - double shared_io_total_time_by_slowest; - double agg_perf_by_slowest; - double agg_time_by_slowest; - double *rank_cumul_io_total_time; - double *rank_cumul_rw_only_time; - double *rank_cumul_md_only_time; -} perf_data_t; - /* * Prototypes */ -void posix_accum_file(struct darshan_posix_file *pfile, hash_entry_t *hfile, int64_t nprocs); -void posix_accum_perf(struct darshan_posix_file *pfile, perf_data_t *pdata); -void posix_calc_file(hash_entry_t *file_hash_table, file_data_t *fdata); void posix_print_total_file(struct darshan_posix_file *pfile, int posix_ver); - -void mpiio_accum_file(struct darshan_mpiio_file *mfile, hash_entry_t *hfile, int64_t nprocs); -void mpiio_accum_perf(struct darshan_mpiio_file *mfile, perf_data_t *pdata); -void mpiio_calc_file(hash_entry_t *file_hash_table, file_data_t *fdata); void mpiio_print_total_file(struct darshan_mpiio_file *mfile, int mpiio_ver); - -void stdio_accum_perf(struct darshan_stdio_file *pfile, perf_data_t *pdata); -void stdio_accum_file(struct darshan_stdio_file *pfile, hash_entry_t *hfile, int64_t nprocs); -void stdio_calc_file(hash_entry_t *file_hash_table, file_data_t *fdata); void stdio_print_total_file(struct darshan_stdio_file *pfile, int stdio_ver); -void calc_perf(perf_data_t *pdata, int64_t nprocs); - int usage (char *exename) { fprintf(stderr, "Usage: %s [options] \n", exename); @@ -226,16 +147,8 @@ int main(int argc, char **argv) int empty_mods = 0; char *mod_buf; - hash_entry_t *file_hash_table = NULL; - hash_entry_t *curr = NULL; - hash_entry_t *tmp_file = NULL; - hash_entry_t total; - file_data_t fdata; - perf_data_t pdata; - - memset(&total, 0, sizeof(total)); - memset(&fdata, 0, sizeof(fdata)); - memset(&pdata, 0, sizeof(pdata)); + darshan_accumulator acc = NULL; + struct darshan_derived_metrics metrics; mask = parse_args(argc, argv, &filename); @@ -372,22 +285,6 @@ int main(int argc, char **argv) printf("# : type of file system that the file resides on.\n"); } - pdata.rank_cumul_io_total_time = malloc(sizeof(double)*job.nprocs); - pdata.rank_cumul_rw_only_time = malloc(sizeof(double)*job.nprocs); - pdata.rank_cumul_md_only_time = malloc(sizeof(double)*job.nprocs); - if (!pdata.rank_cumul_io_total_time || !pdata.rank_cumul_md_only_time || - !pdata.rank_cumul_rw_only_time) - { - darshan_log_close(fd); - return(-1); - } - else - { - memset(pdata.rank_cumul_io_total_time, 0, sizeof(double)*job.nprocs); - memset(pdata.rank_cumul_rw_only_time, 0, sizeof(double)*job.nprocs); - memset(pdata.rank_cumul_md_only_time, 0, sizeof(double)*job.nprocs); - } - mod_buf = malloc(DEF_MOD_BUF_SIZE); if (!mod_buf) { darshan_log_close(fd); @@ -397,7 +294,6 @@ int main(int argc, char **argv) for(i=0; imod_map[i].len == 0) @@ -486,13 +382,16 @@ int main(int argc, char **argv) } } + /* create an accumulator, if supported */ + /* no explicit error checking; we will just skip injecting if null */ + darshan_accumulator_create(i, job.nprocs, &acc); + /* loop over each of this module's records and print them */ while(1) { char *mnt_pt = NULL; char *fs_type = NULL; char *rec_name = NULL; - hash_entry_t *hfile = NULL; ret = mod_logutils[i]->log_get_record(fd, (void **)&mod_buf); if(ret < 1) @@ -543,56 +442,17 @@ int main(int argc, char **argv) mnt_pt, fs_type); } - /* we calculate more detailed stats for POSIX, MPI-IO, and STDIO modules, - * if the parser is executed with more than the base option - */ - if(i != DARSHAN_POSIX_MOD && i != DARSHAN_MPIIO_MOD && i != DARSHAN_STDIO_MOD) - continue; - - HASH_FIND(hlink, file_hash_table, &(base_rec->id), sizeof(darshan_record_id), hfile); - if(!hfile) - { - hfile = malloc(sizeof(*hfile)); - if(!hfile) - { - ret = -1; - goto cleanup; - } - - /* init */ - memset(hfile, 0, sizeof(*hfile)); - hfile->rec_id = base_rec->id; - hfile->type = 0; - hfile->procs = 0; - hfile->rec_dat = NULL; - - HASH_ADD(hlink, file_hash_table,rec_id, sizeof(darshan_record_id), hfile); - } - - if(i == DARSHAN_POSIX_MOD) - { - posix_accum_file((struct darshan_posix_file*)mod_buf, &total, job.nprocs); - posix_accum_file((struct darshan_posix_file*)mod_buf, hfile, job.nprocs); - posix_accum_perf((struct darshan_posix_file*)mod_buf, &pdata); - } - else if(i == DARSHAN_MPIIO_MOD) - { - mpiio_accum_file((struct darshan_mpiio_file*)mod_buf, &total, job.nprocs); - mpiio_accum_file((struct darshan_mpiio_file*)mod_buf, hfile, job.nprocs); - mpiio_accum_perf((struct darshan_mpiio_file*)mod_buf, &pdata); - } - else if(i == DARSHAN_STDIO_MOD) - { - stdio_accum_file((struct darshan_stdio_file*)mod_buf, &total, job.nprocs); - stdio_accum_file((struct darshan_stdio_file*)mod_buf, hfile, job.nprocs); - stdio_accum_perf((struct darshan_stdio_file*)mod_buf, &pdata); - } - - memset(mod_buf, 0, DEF_MOD_BUF_SIZE); + /* accumulated and derived metrics, if supported */ + if(acc) + darshan_accumulator_inject(acc, mod_buf, 1); } if(ret == -1) continue; /* move on to the next module if there was an error with this one */ + /* calculate derived metrics from accumulator */ + if(acc) + darshan_accumulator_emit(acc, &metrics, mod_buf); + /* we calculate more detailed stats for POSIX and MPI-IO modules, * if the parser is executed with more than the base option */ @@ -604,34 +464,21 @@ int main(int argc, char **argv) { if(i == DARSHAN_POSIX_MOD) { - posix_print_total_file((struct darshan_posix_file*)total.rec_dat, fd->mod_ver[i]); + posix_print_total_file((struct darshan_posix_file*)mod_buf, fd->mod_ver[i]); } else if(i == DARSHAN_MPIIO_MOD) { - mpiio_print_total_file((struct darshan_mpiio_file*)total.rec_dat, fd->mod_ver[i]); + mpiio_print_total_file((struct darshan_mpiio_file*)mod_buf, fd->mod_ver[i]); } else if(i == DARSHAN_STDIO_MOD) { - stdio_print_total_file((struct darshan_stdio_file*)total.rec_dat, fd->mod_ver[i]); + stdio_print_total_file((struct darshan_stdio_file*)mod_buf, fd->mod_ver[i]); } } /* File Calc */ if(mask & OPTION_FILE) { - if(i == DARSHAN_POSIX_MOD) - { - posix_calc_file(file_hash_table, &fdata); - } - else if(i == DARSHAN_MPIIO_MOD) - { - mpiio_calc_file(file_hash_table, &fdata); - } - else if(i == DARSHAN_STDIO_MOD) - { - stdio_calc_file(file_hash_table, &fdata); - } - printf("\n# Total file counts\n"); printf("# -----\n"); printf("# : type of file access:\n"); @@ -645,88 +492,76 @@ int main(int argc, char **argv) printf("# maximum byte offset accessed for a file of this type\n"); printf("\n# \n"); printf("# total: %" PRId64 " %" PRId64 " %" PRId64 "\n", - fdata.total, - fdata.total_size, - fdata.total_max); + metrics.category_counters[DARSHAN_ALL_FILES].count, + metrics.category_counters[DARSHAN_ALL_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_ALL_FILES].total_write_volume_bytes, + metrics.category_counters[DARSHAN_ALL_FILES].max_offset_bytes); printf("# read_only: %" PRId64 " %" PRId64 " %" PRId64 "\n", - fdata.read_only, - fdata.read_only_size, - fdata.read_only_max); + metrics.category_counters[DARSHAN_RO_FILES].count, + metrics.category_counters[DARSHAN_RO_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_RO_FILES].total_write_volume_bytes, + metrics.category_counters[DARSHAN_RO_FILES].max_offset_bytes); printf("# write_only: %" PRId64 " %" PRId64 " %" PRId64 "\n", - fdata.write_only, - fdata.write_only_size, - fdata.write_only_max); + metrics.category_counters[DARSHAN_WO_FILES].count, + metrics.category_counters[DARSHAN_WO_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_WO_FILES].total_write_volume_bytes, + metrics.category_counters[DARSHAN_WO_FILES].max_offset_bytes); printf("# read_write: %" PRId64 " %" PRId64 " %" PRId64 "\n", - fdata.read_write, - fdata.read_write_size, - fdata.read_write_max); + metrics.category_counters[DARSHAN_RW_FILES].count, + metrics.category_counters[DARSHAN_RW_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_RW_FILES].total_write_volume_bytes, + metrics.category_counters[DARSHAN_RW_FILES].max_offset_bytes); printf("# unique: %" PRId64 " %" PRId64 " %" PRId64 "\n", - fdata.unique, - fdata.unique_size, - fdata.unique_max); + metrics.category_counters[DARSHAN_UNIQ_FILES].count, + metrics.category_counters[DARSHAN_UNIQ_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_UNIQ_FILES].total_write_volume_bytes, + metrics.category_counters[DARSHAN_UNIQ_FILES].max_offset_bytes); printf("# shared: %" PRId64 " %" PRId64 " %" PRId64 "\n", - fdata.shared, - fdata.shared_size, - fdata.shared_max); + metrics.category_counters[DARSHAN_SHARED_FILES].count + + metrics.category_counters[DARSHAN_PART_SHARED_FILES].count, + metrics.category_counters[DARSHAN_SHARED_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_SHARED_FILES].total_write_volume_bytes + + metrics.category_counters[DARSHAN_PART_SHARED_FILES].total_read_volume_bytes + + metrics.category_counters[DARSHAN_PART_SHARED_FILES].total_write_volume_bytes, + metrics.category_counters[DARSHAN_SHARED_FILES].max_offset_bytes + + metrics.category_counters[DARSHAN_PART_SHARED_FILES].max_offset_bytes); } /* Perf Calc */ if(mask & OPTION_PERF) { - calc_perf(&pdata, job.nprocs); - printf("\n# performance\n"); printf("# -----------\n"); - printf("# total_bytes: %" PRId64 "\n", pdata.total_bytes); + printf("# total_bytes: %" PRId64 "\n", metrics.total_bytes); printf("#\n"); printf("# I/O timing for unique files (seconds):\n"); printf("# ...........................\n"); - printf("# unique files: slowest_rank_io_time: %lf\n", pdata.slowest_rank_io_total_time); - printf("# unique files: slowest_rank_meta_only_time: %lf\n", pdata.slowest_rank_meta_only_time); - printf("# unique files: slowest_rank_rw_only_time: %lf\n", pdata.slowest_rank_rw_only_time); - printf("# unique files: slowest_rank: %d\n", pdata.slowest_rank_rank); + printf("# unique files: slowest_rank_io_time: %lf\n", metrics.unique_io_total_time_by_slowest); + printf("# unique files: slowest_rank_meta_only_time: %lf\n", metrics.unique_md_only_time_by_slowest); + printf("# unique files: slowest_rank_rw_only_time: %lf\n", metrics.unique_rw_only_time_by_slowest); + printf("# unique files: slowest_rank: %d\n", metrics.unique_io_slowest_rank); printf("#\n"); printf("# I/O timing for shared files (seconds):\n"); printf("# ...........................\n"); - printf("# shared files: time_by_slowest: %lf\n", pdata.shared_io_total_time_by_slowest); + printf("# shared files: time_by_slowest: %lf\n", metrics.shared_io_total_time_by_slowest); printf("#\n"); printf("# Aggregate performance, including both shared and unique files:\n"); printf("# ...........................\n"); - printf("# agg_time_by_slowest: %lf # seconds\n", pdata.agg_time_by_slowest); - printf("# agg_perf_by_slowest: %lf # MiB/s\n", pdata.agg_perf_by_slowest); + printf("# agg_time_by_slowest: %lf # seconds\n", metrics.agg_time_by_slowest); + printf("# agg_perf_by_slowest: %lf # MiB/s\n", metrics.agg_perf_by_slowest); } - /* reset data structures for next module */ - if(total.rec_dat) free(total.rec_dat); - memset(&total, 0, sizeof(total)); - memset(&fdata, 0, sizeof(fdata)); - save_io_total = pdata.rank_cumul_io_total_time; - save_rw_only = pdata.rank_cumul_rw_only_time; - save_md_only = pdata.rank_cumul_md_only_time; - memset(&pdata, 0, sizeof(pdata)); - memset(save_io_total, 0, sizeof(double)*job.nprocs); - memset(save_rw_only, 0, sizeof(double)*job.nprocs); - memset(save_md_only, 0, sizeof(double)*job.nprocs); - pdata.rank_cumul_io_total_time = save_io_total; - pdata.rank_cumul_md_only_time = save_md_only; - pdata.rank_cumul_rw_only_time = save_rw_only; - - HASH_ITER(hlink, file_hash_table, curr, tmp_file) - { - HASH_DELETE(hlink, file_hash_table, curr); - if(curr->rec_dat) free(curr->rec_dat); - free(curr); + if(acc) { + darshan_accumulator_destroy(acc); + acc = NULL; } } + if(empty_mods == DARSHAN_MAX_MODS) printf("\n# no module data available.\n"); ret = 0; -cleanup: darshan_log_close(fd); - free(pdata.rank_cumul_io_total_time); - free(pdata.rank_cumul_md_only_time); - free(pdata.rank_cumul_rw_only_time); free(mod_buf); /* free record hash data */ @@ -746,481 +581,6 @@ int main(int argc, char **argv) return(ret); } -void stdio_accum_file(struct darshan_stdio_file *pfile, - hash_entry_t *hfile, - int64_t nprocs) -{ - struct darshan_stdio_file* tmp; - - hfile->procs += 1; - - if(pfile->base_rec.rank == -1) - { - hfile->procs = nprocs; - hfile->type |= FILETYPE_SHARED; - - } - else if(hfile->procs > 1) - { - hfile->type &= (~FILETYPE_UNIQUE); - hfile->type |= FILETYPE_PARTSHARED; - } - else - { - hfile->type |= FILETYPE_UNIQUE; - } - - if(hfile->rec_dat == NULL) - { - /* generate empty base record to start accumulation */ - hfile->rec_dat = malloc(sizeof(struct darshan_stdio_file)); - assert(hfile->rec_dat); - memset(hfile->rec_dat, 0, sizeof(struct darshan_stdio_file)); - tmp = (struct darshan_stdio_file*)hfile->rec_dat; - mod_logutils[DARSHAN_STDIO_MOD]->log_agg_records(pfile, tmp, 1); - } - else - { - tmp = (struct darshan_stdio_file*)hfile->rec_dat; - mod_logutils[DARSHAN_STDIO_MOD]->log_agg_records(pfile, tmp, 0); - } - - return; -} - -void posix_accum_file(struct darshan_posix_file *pfile, - hash_entry_t *hfile, - int64_t nprocs) -{ - struct darshan_posix_file* tmp; - - hfile->procs += 1; - - if(pfile->base_rec.rank == -1) - { - hfile->procs = nprocs; - hfile->type |= FILETYPE_SHARED; - - } - else if(hfile->procs > 1) - { - hfile->type &= (~FILETYPE_UNIQUE); - hfile->type |= FILETYPE_PARTSHARED; - } - else - { - hfile->type |= FILETYPE_UNIQUE; - } - - if(hfile->rec_dat == NULL) - { - /* generate empty base record to start accumulation */ - hfile->rec_dat = malloc(sizeof(struct darshan_posix_file)); - assert(hfile->rec_dat); - memset(hfile->rec_dat, 0, sizeof(struct darshan_posix_file)); - tmp = (struct darshan_posix_file*)hfile->rec_dat; - mod_logutils[DARSHAN_POSIX_MOD]->log_agg_records(pfile, tmp, 1); - } - else - { - tmp = (struct darshan_posix_file*)hfile->rec_dat; - mod_logutils[DARSHAN_POSIX_MOD]->log_agg_records(pfile, tmp, 0); - } - - return; -} - -void mpiio_accum_file(struct darshan_mpiio_file *mfile, - hash_entry_t *hfile, - int64_t nprocs) -{ - struct darshan_mpiio_file* tmp; - - hfile->procs += 1; - - if(mfile->base_rec.rank == -1) - { - hfile->procs = nprocs; - hfile->type |= FILETYPE_SHARED; - - } - else if(hfile->procs > 1) - { - hfile->type &= (~FILETYPE_UNIQUE); - hfile->type |= FILETYPE_PARTSHARED; - } - else - { - hfile->type |= FILETYPE_UNIQUE; - } - - if(hfile->rec_dat == NULL) - { - /* generate empty base record to start accumulation */ - hfile->rec_dat = malloc(sizeof(struct darshan_mpiio_file)); - assert(hfile->rec_dat); - memset(hfile->rec_dat, 0, sizeof(struct darshan_mpiio_file)); - tmp = (struct darshan_mpiio_file*)hfile->rec_dat; - mod_logutils[DARSHAN_MPIIO_MOD]->log_agg_records(mfile, tmp, 1); - } - else - { - tmp = (struct darshan_mpiio_file*)hfile->rec_dat; - mod_logutils[DARSHAN_MPIIO_MOD]->log_agg_records(mfile, tmp, 0); - } - - return; -} - -void stdio_accum_perf(struct darshan_stdio_file *pfile, - perf_data_t *pdata) -{ - pdata->total_bytes += pfile->counters[STDIO_BYTES_READ] + - pfile->counters[STDIO_BYTES_WRITTEN]; - - /* - * Calculation of Shared File Time - * by_slowest: use slowest rank time from log data - * (most accurate but requires newer log version) - */ - if(pfile->base_rec.rank == -1) - { - /* by_slowest */ - pdata->shared_io_total_time_by_slowest += - pfile->fcounters[STDIO_F_SLOWEST_RANK_TIME]; - } - - /* - * Calculation of Unique File Time - * record the data for each file and sum it - */ - else - { - pdata->rank_cumul_io_total_time[pfile->base_rec.rank] += - (pfile->fcounters[STDIO_F_META_TIME] + - pfile->fcounters[STDIO_F_READ_TIME] + - pfile->fcounters[STDIO_F_WRITE_TIME]); - pdata->rank_cumul_md_only_time[pfile->base_rec.rank] += - pfile->fcounters[STDIO_F_META_TIME]; - pdata->rank_cumul_rw_only_time[pfile->base_rec.rank] += - pfile->fcounters[STDIO_F_READ_TIME] + - pfile->fcounters[STDIO_F_WRITE_TIME]; - } - - return; -} - - -void posix_accum_perf(struct darshan_posix_file *pfile, - perf_data_t *pdata) -{ - pdata->total_bytes += pfile->counters[POSIX_BYTES_READ] + - pfile->counters[POSIX_BYTES_WRITTEN]; - - /* - * Calculation of Shared File Time - * by_slowest: use slowest rank time from log data - * (most accurate but requires newer log version) - */ - if(pfile->base_rec.rank == -1) - { - /* by_slowest */ - pdata->shared_io_total_time_by_slowest += - pfile->fcounters[POSIX_F_SLOWEST_RANK_TIME]; - } - - /* - * Calculation of Unique File Time - * record the data for each file and sum it - */ - else - { - pdata->rank_cumul_io_total_time[pfile->base_rec.rank] += - (pfile->fcounters[POSIX_F_META_TIME] + - pfile->fcounters[POSIX_F_READ_TIME] + - pfile->fcounters[POSIX_F_WRITE_TIME]); - pdata->rank_cumul_md_only_time[pfile->base_rec.rank] += - pfile->fcounters[POSIX_F_META_TIME]; - pdata->rank_cumul_rw_only_time[pfile->base_rec.rank] += - pfile->fcounters[POSIX_F_READ_TIME] + - pfile->fcounters[POSIX_F_WRITE_TIME]; - } - - return; -} - -void mpiio_accum_perf(struct darshan_mpiio_file *mfile, - perf_data_t *pdata) -{ - pdata->total_bytes += mfile->counters[MPIIO_BYTES_READ] + - mfile->counters[MPIIO_BYTES_WRITTEN]; - - /* - * Calculation of Shared File Time - * by_slowest: use slowest rank time from log data - * (most accurate but requires newer log version) - */ - if(mfile->base_rec.rank == -1) - { - /* by_slowest */ - pdata->shared_io_total_time_by_slowest += - mfile->fcounters[MPIIO_F_SLOWEST_RANK_TIME]; - } - - /* - * Calculation of Unique File Time - * record the data for each file and sum it - */ - else - { - pdata->rank_cumul_io_total_time[mfile->base_rec.rank] += - (mfile->fcounters[MPIIO_F_META_TIME] + - mfile->fcounters[MPIIO_F_READ_TIME] + - mfile->fcounters[MPIIO_F_WRITE_TIME]); - pdata->rank_cumul_md_only_time[mfile->base_rec.rank] += - mfile->fcounters[MPIIO_F_META_TIME]; - pdata->rank_cumul_rw_only_time[mfile->base_rec.rank] += - mfile->fcounters[MPIIO_F_READ_TIME] + - mfile->fcounters[MPIIO_F_WRITE_TIME]; - } - - return; -} - -void stdio_calc_file(hash_entry_t *file_hash_table, - file_data_t *fdata) -{ - hash_entry_t *curr = NULL; - hash_entry_t *tmp = NULL; - struct darshan_stdio_file *file_rec; - - memset(fdata, 0, sizeof(*fdata)); - HASH_ITER(hlink, file_hash_table, curr, tmp) - { - int64_t bytes; - int64_t r; - int64_t w; - - file_rec = (struct darshan_stdio_file*)curr->rec_dat; - assert(file_rec); - - bytes = file_rec->counters[STDIO_BYTES_READ] + - file_rec->counters[STDIO_BYTES_WRITTEN]; - - r = file_rec->counters[STDIO_READS]; - - w = file_rec->counters[STDIO_WRITES]; - - fdata->total += 1; - fdata->total_size += bytes; - fdata->total_max = max(fdata->total_max, bytes); - - if (r && !w) - { - fdata->read_only += 1; - fdata->read_only_size += bytes; - fdata->read_only_max = max(fdata->read_only_max, bytes); - } - - if (!r && w) - { - fdata->write_only += 1; - fdata->write_only_size += bytes; - fdata->write_only_max = max(fdata->write_only_max, bytes); - } - - if (r && w) - { - fdata->read_write += 1; - fdata->read_write_size += bytes; - fdata->read_write_max = max(fdata->read_write_max, bytes); - } - - if ((curr->type & (FILETYPE_SHARED|FILETYPE_PARTSHARED))) - { - fdata->shared += 1; - fdata->shared_size += bytes; - fdata->shared_max = max(fdata->shared_max, bytes); - } - - if ((curr->type & (FILETYPE_UNIQUE))) - { - fdata->unique += 1; - fdata->unique_size += bytes; - fdata->unique_max = max(fdata->unique_max, bytes); - } - } - - return; -} - - -void posix_calc_file(hash_entry_t *file_hash_table, - file_data_t *fdata) -{ - hash_entry_t *curr = NULL; - hash_entry_t *tmp = NULL; - struct darshan_posix_file *file_rec; - - memset(fdata, 0, sizeof(*fdata)); - HASH_ITER(hlink, file_hash_table, curr, tmp) - { - int64_t bytes; - int64_t r; - int64_t w; - - file_rec = (struct darshan_posix_file*)curr->rec_dat; - assert(file_rec); - - bytes = file_rec->counters[POSIX_BYTES_READ] + - file_rec->counters[POSIX_BYTES_WRITTEN]; - - r = file_rec->counters[POSIX_READS]; - - w = file_rec->counters[POSIX_WRITES]; - - fdata->total += 1; - fdata->total_size += bytes; - fdata->total_max = max(fdata->total_max, bytes); - - if (r && !w) - { - fdata->read_only += 1; - fdata->read_only_size += bytes; - fdata->read_only_max = max(fdata->read_only_max, bytes); - } - - if (!r && w) - { - fdata->write_only += 1; - fdata->write_only_size += bytes; - fdata->write_only_max = max(fdata->write_only_max, bytes); - } - - if (r && w) - { - fdata->read_write += 1; - fdata->read_write_size += bytes; - fdata->read_write_max = max(fdata->read_write_max, bytes); - } - - if ((curr->type & (FILETYPE_SHARED|FILETYPE_PARTSHARED))) - { - fdata->shared += 1; - fdata->shared_size += bytes; - fdata->shared_max = max(fdata->shared_max, bytes); - } - - if ((curr->type & (FILETYPE_UNIQUE))) - { - fdata->unique += 1; - fdata->unique_size += bytes; - fdata->unique_max = max(fdata->unique_max, bytes); - } - } - - return; -} - -void mpiio_calc_file(hash_entry_t *file_hash_table, - file_data_t *fdata) -{ - hash_entry_t *curr = NULL; - hash_entry_t *tmp = NULL; - struct darshan_mpiio_file *file_rec; - - memset(fdata, 0, sizeof(*fdata)); - HASH_ITER(hlink, file_hash_table, curr, tmp) - { - int64_t bytes; - int64_t r; - int64_t w; - - file_rec = (struct darshan_mpiio_file*)curr->rec_dat; - assert(file_rec); - - bytes = file_rec->counters[MPIIO_BYTES_READ] + - file_rec->counters[MPIIO_BYTES_WRITTEN]; - - r = (file_rec->counters[MPIIO_INDEP_READS]+ - file_rec->counters[MPIIO_COLL_READS] + - file_rec->counters[MPIIO_SPLIT_READS] + - file_rec->counters[MPIIO_NB_READS]); - - w = (file_rec->counters[MPIIO_INDEP_WRITES]+ - file_rec->counters[MPIIO_COLL_WRITES] + - file_rec->counters[MPIIO_SPLIT_WRITES] + - file_rec->counters[MPIIO_NB_WRITES]); - - fdata->total += 1; - fdata->total_size += bytes; - fdata->total_max = max(fdata->total_max, bytes); - - if (r && !w) - { - fdata->read_only += 1; - fdata->read_only_size += bytes; - fdata->read_only_max = max(fdata->read_only_max, bytes); - } - - if (!r && w) - { - fdata->write_only += 1; - fdata->write_only_size += bytes; - fdata->write_only_max = max(fdata->write_only_max, bytes); - } - - if (r && w) - { - fdata->read_write += 1; - fdata->read_write_size += bytes; - fdata->read_write_max = max(fdata->read_write_max, bytes); - } - - if ((curr->type & (FILETYPE_SHARED|FILETYPE_PARTSHARED))) - { - fdata->shared += 1; - fdata->shared_size += bytes; - fdata->shared_max = max(fdata->shared_max, bytes); - } - - if ((curr->type & (FILETYPE_UNIQUE))) - { - fdata->unique += 1; - fdata->unique_size += bytes; - fdata->unique_max = max(fdata->unique_max, bytes); - } - } - - return; -} - -void calc_perf(perf_data_t *pdata, - int64_t nprocs) -{ - int64_t i; - - for (i=0; irank_cumul_io_total_time[i] > pdata->slowest_rank_io_total_time) - { - pdata->slowest_rank_io_total_time = pdata->rank_cumul_io_total_time[i]; - pdata->slowest_rank_meta_only_time = pdata->rank_cumul_md_only_time[i]; - pdata->slowest_rank_rw_only_time = pdata->rank_cumul_rw_only_time[i]; - pdata->slowest_rank_rank = i; - } - } - - if (pdata->slowest_rank_io_total_time + pdata->shared_io_total_time_by_slowest) - pdata->agg_perf_by_slowest = ((double)pdata->total_bytes / 1048576.0) / - (pdata->slowest_rank_io_total_time + - pdata->shared_io_total_time_by_slowest); - pdata->agg_time_by_slowest = pdata->slowest_rank_io_total_time + - pdata->shared_io_total_time_by_slowest; - - return; -} - void stdio_print_total_file(struct darshan_stdio_file *pfile, int stdio_ver) { int i; diff --git a/darshan-util/darshan-posix-logutils.c b/darshan-util/darshan-posix-logutils.c index 8cef7397f..2b20ff318 100644 --- a/darshan-util/darshan-posix-logutils.c +++ b/darshan-util/darshan-posix-logutils.c @@ -22,6 +22,8 @@ #include "darshan-logutils.h" +#define max(a,b) (((a) > (b)) ? (a) : (b)) + /* counter name strings for the POSIX module */ #define X(a) #a, char *posix_counter_names[] = { @@ -45,6 +47,17 @@ static void darshan_log_print_posix_description(int ver); static void darshan_log_print_posix_file_diff(void *file_rec1, char *file_name1, void *file_rec2, char *file_name2); static void darshan_log_agg_posix_files(void *rec, void *agg_rec, int init_flag); +static int darshan_log_sizeof_posix_file(void* posix_buf_p); +static int darshan_log_record_metrics_posix_file(void* posix_buf_p, + uint64_t* rec_id, + int64_t* r_bytes, + int64_t* w_bytes, + int64_t* max_offset, + double* io_total_time, + double* md_only_time, + double* rw_only_time, + int64_t* rank, + int64_t* nprocs); struct darshan_mod_logutil_funcs posix_logutils = { @@ -54,8 +67,68 @@ struct darshan_mod_logutil_funcs posix_logutils = .log_print_description = &darshan_log_print_posix_description, .log_print_diff = &darshan_log_print_posix_file_diff, .log_agg_records = &darshan_log_agg_posix_files, + .log_sizeof_record = &darshan_log_sizeof_posix_file, + .log_record_metrics = &darshan_log_record_metrics_posix_file }; +static int darshan_log_sizeof_posix_file(void* posix_buf_p) +{ + /* posix records have a fixed size */ + return(sizeof(struct darshan_posix_file)); +} + +static int darshan_log_record_metrics_posix_file(void* posix_buf_p, + uint64_t* rec_id, + int64_t* r_bytes, + int64_t* w_bytes, + int64_t* max_offset, + double* io_total_time, + double* md_only_time, + double* rw_only_time, + int64_t* rank, + int64_t* nprocs) +{ + struct darshan_posix_file *psx_rec = (struct darshan_posix_file *)posix_buf_p; + + *rec_id = psx_rec->base_rec.id; + *r_bytes = psx_rec->counters[POSIX_BYTES_READ]; + *w_bytes = psx_rec->counters[POSIX_BYTES_WRITTEN]; + + *max_offset = max(psx_rec->counters[POSIX_MAX_BYTE_READ], psx_rec->counters[POSIX_MAX_BYTE_WRITTEN]); + + *rank = psx_rec->base_rec.rank; + /* nprocs is 1 per record, unless rank is negative, in which case we + * report -1 as the rank value to represent "all" + */ + if(psx_rec->base_rec.rank < 0) + *nprocs = -1; + else + *nprocs = 1; + + if(psx_rec->base_rec.rank < 0) { + /* shared file records populate a counter with the slowest rank time + * (derived during reduction). They do not have a breakdown of meta + * and rw time, though. + */ + *io_total_time = psx_rec->fcounters[POSIX_F_SLOWEST_RANK_TIME]; + *md_only_time = 0; + *rw_only_time = 0; + } + else { + /* non-shared records have separate meta, read, and write values + * that we can combine as needed + */ + *io_total_time = psx_rec->fcounters[POSIX_F_META_TIME] + + psx_rec->fcounters[POSIX_F_READ_TIME] + + psx_rec->fcounters[POSIX_F_WRITE_TIME]; + *md_only_time = psx_rec->fcounters[POSIX_F_META_TIME]; + *rw_only_time = psx_rec->fcounters[POSIX_F_READ_TIME] + + psx_rec->fcounters[POSIX_F_WRITE_TIME]; + } + + return(0); +} + static int darshan_log_get_posix_file(darshan_fd fd, void** posix_buf_p) { struct darshan_posix_file *file = *((struct darshan_posix_file **)posix_buf_p); @@ -439,6 +512,7 @@ static void darshan_log_agg_posix_files(void *rec, void *agg_rec, int init_flag) int total_count; int64_t tmp_val[4]; int64_t tmp_cnt[4]; + int duplicate_mask[4] = {0}; int tmp_ndx; int64_t psx_fastest_rank, psx_slowest_rank, psx_fastest_bytes, psx_slowest_bytes; @@ -587,6 +661,12 @@ static void darshan_log_agg_posix_files(void *rec, void *agg_rec, int init_flag) break; case POSIX_STRIDE1_STRIDE: case POSIX_ACCESS1_ACCESS: + /* NOTE: this same code block is used to collapse both the + * ACCESS and STRIDE counter sets (see the drop through in + * the case above). We therefore have to take care to zero + * any stateful variables that might get reused. + */ + memset(duplicate_mask, 0, 4*sizeof(duplicate_mask[0])); /* increment common value counters */ /* first, collapse duplicates */ @@ -597,7 +677,8 @@ static void darshan_log_agg_posix_files(void *rec, void *agg_rec, int init_flag) if(agg_psx_rec->counters[i + k] == psx_rec->counters[j]) { agg_psx_rec->counters[i + k + 4] += psx_rec->counters[j + 4]; - psx_rec->counters[j] = psx_rec->counters[j + 4] = 0; + /* flag that we should ignore this one now */ + duplicate_mask[j-i] = 1; } } } @@ -605,6 +686,9 @@ static void darshan_log_agg_posix_files(void *rec, void *agg_rec, int init_flag) /* second, add new counters */ for(j = i; j < i + 4; j++) { + /* skip any that were handled above already */ + if(duplicate_mask[j-i]) + continue; tmp_ndx = 0; memset(tmp_val, 0, 4 * sizeof(int64_t)); memset(tmp_cnt, 0, 4 * sizeof(int64_t)); diff --git a/darshan-util/darshan-stdio-logutils.c b/darshan-util/darshan-stdio-logutils.c index 81868a9db..315bb576c 100644 --- a/darshan-util/darshan-stdio-logutils.c +++ b/darshan-util/darshan-stdio-logutils.c @@ -22,6 +22,8 @@ #include "darshan-logutils.h" +#define max(a,b) (((a) > (b)) ? (a) : (b)) + /* integer counter name strings for the STDIO module */ #define X(a) #a, char *stdio_counter_names[] = { @@ -45,6 +47,17 @@ static void darshan_log_print_stdio_description(int ver); static void darshan_log_print_stdio_record_diff(void *file_rec1, char *file_name1, void *file_rec2, char *file_name2); static void darshan_log_agg_stdio_records(void *rec, void *agg_rec, int init_flag); +static int darshan_log_sizeof_stdio_record(void* stdio_buf_p); +static int darshan_log_record_metrics_stdio_record(void* stdio_buf_p, + uint64_t* rec_id, + int64_t* r_bytes, + int64_t* w_bytes, + int64_t* max_offset, + double* io_total_time, + double* md_only_time, + double* rw_only_time, + int64_t* rank, + int64_t* nprocs); /* structure storing each function needed for implementing the darshan * logutil interface. these functions are used for reading, writing, and @@ -57,9 +70,69 @@ struct darshan_mod_logutil_funcs stdio_logutils = .log_print_record = &darshan_log_print_stdio_record, .log_print_description = &darshan_log_print_stdio_description, .log_print_diff = &darshan_log_print_stdio_record_diff, - .log_agg_records = &darshan_log_agg_stdio_records + .log_agg_records = &darshan_log_agg_stdio_records, + .log_sizeof_record = &darshan_log_sizeof_stdio_record, + .log_record_metrics = &darshan_log_record_metrics_stdio_record }; +static int darshan_log_sizeof_stdio_record(void* stdio_buf_p) +{ + /* stdio records have a fixed size */ + return(sizeof(struct darshan_stdio_file)); +} + +static int darshan_log_record_metrics_stdio_record(void* stdio_buf_p, + uint64_t* rec_id, + int64_t* r_bytes, + int64_t* w_bytes, + int64_t* max_offset, + double* io_total_time, + double* md_only_time, + double* rw_only_time, + int64_t* rank, + int64_t* nprocs) +{ + struct darshan_stdio_file *stdio_rec = (struct darshan_stdio_file *)stdio_buf_p; + + *rec_id = stdio_rec->base_rec.id; + *r_bytes = stdio_rec->counters[STDIO_BYTES_READ]; + *w_bytes = stdio_rec->counters[STDIO_BYTES_WRITTEN]; + + *max_offset = max(stdio_rec->counters[STDIO_MAX_BYTE_READ], stdio_rec->counters[STDIO_MAX_BYTE_WRITTEN]); + + *rank = stdio_rec->base_rec.rank; + /* nprocs is 1 per record, unless rank is negative, in which case we + * report -1 as the rank value to represent "all" + */ + if(stdio_rec->base_rec.rank < 0) + *nprocs = -1; + else + *nprocs = 1; + + if(stdio_rec->base_rec.rank < 0) { + /* shared file records populate a counter with the slowest rank time + * (derived during reduction). They do not have a breakdown of meta + * and rw time, though. + */ + *io_total_time = stdio_rec->fcounters[STDIO_F_SLOWEST_RANK_TIME]; + *md_only_time = 0; + *rw_only_time = 0; + } + else { + /* non-shared records have separate meta, read, and write values + * that we can combine as needed + */ + *io_total_time = stdio_rec->fcounters[STDIO_F_META_TIME] + + stdio_rec->fcounters[STDIO_F_READ_TIME] + + stdio_rec->fcounters[STDIO_F_WRITE_TIME]; + *md_only_time = stdio_rec->fcounters[STDIO_F_META_TIME]; + *rw_only_time = stdio_rec->fcounters[STDIO_F_READ_TIME] + + stdio_rec->fcounters[STDIO_F_WRITE_TIME]; + } + + return(0); +} + /* retrieve a STDIO record from log file descriptor 'fd', storing the * data in the buffer address pointed to by 'stdio_buf_p'. Return 1 on * successful record read, 0 on no more data, and -1 on error. diff --git a/darshan-util/tests/unit-tests/Makefile.subdir b/darshan-util/tests/unit-tests/Makefile.subdir new file mode 100644 index 000000000..ee7589c05 --- /dev/null +++ b/darshan-util/tests/unit-tests/Makefile.subdir @@ -0,0 +1,13 @@ +check_PROGRAMS += \ + tests/unit-tests/darshan-accumulator + +TESTS += \ + tests/unit-tests/darshan-accumulator + +tests_unit_tests_darshan_accumulator_SOURCES = \ + tests/unit-tests/darshan-accumulator.c \ + tests/unit-tests/munit/munit.c +tests_unit_tests_darshan_accumulator_LDADD = libdarshan-util.la + +noinst_HEADERS += \ + tests/unit-tests/munit/munit.h diff --git a/darshan-util/tests/unit-tests/README.md b/darshan-util/tests/unit-tests/README.md new file mode 100644 index 000000000..da61bb68b --- /dev/null +++ b/darshan-util/tests/unit-tests/README.md @@ -0,0 +1,7 @@ +# Unit tests + +This subdirectory contains unit tests cases that can be executed as part of +"make check" for the darshan-util directory + +The tests in this subdirectory use munit to coordinate test cases, see +https://nemequ.github.io/munit/ diff --git a/darshan-util/tests/unit-tests/darshan-accumulator.c b/darshan-util/tests/unit-tests/darshan-accumulator.c new file mode 100644 index 000000000..6a71b082d --- /dev/null +++ b/darshan-util/tests/unit-tests/darshan-accumulator.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) 2022 University of Chicago. + * See COPYRIGHT notice in top-level directory. + * + */ + +#include +#include "munit/munit.h" + +#include + +static MunitResult inject_shared_file_records(const MunitParameter params[], void* data); +static MunitResult inject_unique_file_records(const MunitParameter params[], void* data); +static void* test_context_setup(const MunitParameter params[], void* user_data); +static void test_context_tear_down(void *data); + +static void posix_set_dummy_record(void* buffer); +static void posix_validate_double_dummy_record(void* buffer, struct darshan_derived_metrics* metrics, int shared_file_flag); +static void stdio_set_dummy_record(void* buffer); +static void stdio_validate_double_dummy_record(void* buffer, struct darshan_derived_metrics* metrics, int shared_file_flag); +static void mpiio_set_dummy_record(void* buffer); +static void mpiio_validate_double_dummy_record(void* buffer, struct darshan_derived_metrics* metrics, int shared_file_flag); + + +/* test definition */ +static char* module_name_params[] = {"POSIX", "STDIO", "MPI-IO", NULL}; + +static MunitParameterEnum test_params[] + = {{"module_name", module_name_params}, {NULL, NULL}}; + +static MunitTest tests[] + = {{"/inject-shared-file-records", inject_shared_file_records, + test_context_setup, test_context_tear_down, MUNIT_TEST_OPTION_NONE, + test_params}, + {"/inject-unique-file-records", inject_unique_file_records, + test_context_setup, test_context_tear_down, MUNIT_TEST_OPTION_NONE, + test_params}, + {NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}}; + +static const MunitSuite test_suite = { + "/darshan-accumulator", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE +}; + + +/* function pointers for testing each module type */ +void (*set_dummy_fn[DARSHAN_KNOWN_MODULE_COUNT])(void*) = { + NULL, /* DARSHAN_NULL_MOD */ + posix_set_dummy_record, /* DARSHAN_POSIX_MOD */ + mpiio_set_dummy_record, /* DARSHAN_MPIIO_MOD */ + NULL, /* DARSHAN_H5F_MOD */ + NULL, /* DARSHAN_H5D_MOD */ + NULL, /* DARSHAN_PNETCDF_MOD */ + NULL, /* DARSHAN_BGQ_MOD */ + NULL, /* DARSHAN_LUSTRE_MOD */ + stdio_set_dummy_record, /* DARSHAN_STDIO_MOD */ + NULL, /* DXT_POSIX_MOD */ + NULL, /* DXT_MPIIO_MOD */ + NULL, /* DARSHAN_MDHIM_MOD */ + NULL, /* DARSHAN_APXC_MOD */ + NULL, /* DARSHAN_APMPI_MOD */ + NULL /* DARSHAN_HEATMAP_MOD */ +}; + +void (*validate_double_dummy_fn[DARSHAN_KNOWN_MODULE_COUNT])(void*, struct darshan_derived_metrics*, int) = { + NULL, /* DARSHAN_NULL_MOD */ + posix_validate_double_dummy_record, /* DARSHAN_POSIX_MOD */ + mpiio_validate_double_dummy_record, /* DARSHAN_MPIIO_MOD */ + NULL, /* DARSHAN_H5F_MOD */ + NULL, /* DARSHAN_H5D_MOD */ + NULL, /* DARSHAN_PNETCDF_MOD */ + NULL, /* DARSHAN_BGQ_MOD */ + NULL, /* DARSHAN_LUSTRE_MOD */ + stdio_validate_double_dummy_record, /* DARSHAN_STDIO_MOD */ + NULL, /* DXT_POSIX_MOD */ + NULL, /* DXT_MPIIO_MOD */ + NULL, /* DARSHAN_MDHIM_MOD */ + NULL, /* DARSHAN_APXC_MOD */ + NULL, /* DARSHAN_APMPI_MOD */ + NULL /* DARSHAN_HEATMAP_MOD */ +}; + +struct test_context { + darshan_module_id mod_id; + struct darshan_mod_logutil_funcs* mod_fns; +}; + +static void* test_context_setup(const MunitParameter params[], void* user_data) +{ + (void) user_data; + int i; + int found = 0; + + const char* module_name = munit_parameters_get(params, "module_name"); + + struct test_context* ctx = calloc(1, sizeof(*ctx)); + munit_assert_not_null(ctx); + + /* Look for module with this name and keep a reference to it's logutils + * functions. + */ + for(i=0; imod_id = i; + ctx->mod_fns = mod_logutils[i]; + found=1; + break; + } + } + munit_assert_int(found, ==, 1); + + return ctx; +} + +static void test_context_tear_down(void *data) +{ + struct test_context *ctx = (struct test_context*)data; + + free(ctx); +} + +/* test accumulating data on shared files (shared in the sense that multiple + * ranks opened the same file but produced distinct records) + */ +static MunitResult inject_shared_file_records(const MunitParameter params[], void* data) +{ + struct test_context* ctx = (struct test_context*)data; + int ret; + darshan_accumulator acc; + struct darshan_derived_metrics metrics; + void* record1; + void* record2; + void* record_agg; + struct darshan_base_record* base_rec; + + record1 = malloc(DEF_MOD_BUF_SIZE); + munit_assert_not_null(record1); + record2 = malloc(DEF_MOD_BUF_SIZE); + munit_assert_not_null(record2); + record_agg = malloc(DEF_MOD_BUF_SIZE); + munit_assert_not_null(record_agg); + + /* make sure we have a function defined to set example records */ + munit_assert_not_null(set_dummy_fn[ctx->mod_id]); + + /* create example records, shared file but different ranks */ + set_dummy_fn[ctx->mod_id](record1); + set_dummy_fn[ctx->mod_id](record2); + base_rec = record2; + base_rec->rank++; + + /**** shared file aggregation ****/ + + ret = darshan_accumulator_create(ctx->mod_id, 4, &acc); + munit_assert_int(ret, ==, 0); + + /* inject two example records */ + ret = darshan_accumulator_inject(acc, record1, 1); + munit_assert_int(ret, ==, 0); + ret = darshan_accumulator_inject(acc, record2, 1); + munit_assert_int(ret, ==, 0); + + /* emit results */ + ret = darshan_accumulator_emit(acc, &metrics, record_agg); + munit_assert_int(ret, ==, 0); + + /* sanity check */ + validate_double_dummy_fn[ctx->mod_id](record_agg, &metrics, 1); + + ret = darshan_accumulator_destroy(acc); + munit_assert_int(ret, ==, 0); + + free(record1); + free(record2); + free(record_agg); + + return MUNIT_OK; +} + +/* test accumulating data on unique files */ +static MunitResult inject_unique_file_records(const MunitParameter params[], void* data) +{ + struct test_context* ctx = (struct test_context*)data; + int ret; + darshan_accumulator acc; + struct darshan_derived_metrics metrics; + void* record1; + void* record2; + void* record_agg; + struct darshan_base_record* base_rec; + + record1 = malloc(DEF_MOD_BUF_SIZE); + munit_assert_not_null(record1); + record2 = malloc(DEF_MOD_BUF_SIZE); + munit_assert_not_null(record2); + record_agg = malloc(DEF_MOD_BUF_SIZE); + munit_assert_not_null(record_agg); + + /* make sure we have a function defined to set example records */ + munit_assert_not_null(set_dummy_fn[ctx->mod_id]); + + /* create example records, shared file but different ranks */ + set_dummy_fn[ctx->mod_id](record1); + set_dummy_fn[ctx->mod_id](record2); + base_rec = record2; + base_rec->rank++; + + /**** unique file aggregation ****/ + + /* change id hash in one record */ + base_rec->id++; + + ret = darshan_accumulator_create(ctx->mod_id, 4, &acc); + munit_assert_int(ret, ==, 0); + + /* inject two example records */ + ret = darshan_accumulator_inject(acc, record1, 1); + munit_assert_int(ret, ==, 0); + ret = darshan_accumulator_inject(acc, record2, 1); + munit_assert_int(ret, ==, 0); + + /* emit results */ + ret = darshan_accumulator_emit(acc, &metrics, record_agg); + munit_assert_int(ret, ==, 0); + + /* sanity check */ + validate_double_dummy_fn[ctx->mod_id](record_agg, &metrics, 0); + + ret = darshan_accumulator_destroy(acc); + munit_assert_int(ret, ==, 0); + + free(record1); + free(record2); + free(record_agg); + + return MUNIT_OK; +} + + +int main(int argc, char **argv) +{ + return munit_suite_main(&test_suite, NULL, argc, argv); +} + +/* Set example values for record of type posix. As elsewhere in the + * logutils API, the size of the buffer is implied. + */ +static void posix_set_dummy_record(void* buffer) { + struct darshan_posix_file* pfile = buffer; + + /* This function must be updated (or at least checked) if the posix + * module log format changes + */ + munit_assert_int(DARSHAN_POSIX_VER, ==, 4); + + pfile->base_rec.id = 15574190512568163195UL; + pfile->base_rec.rank = 0; + + pfile->counters[POSIX_OPENS] = 16; + pfile->counters[POSIX_FILENOS] = 0; + pfile->counters[POSIX_DUPS] = 0; + pfile->counters[POSIX_READS] = 4; + pfile->counters[POSIX_WRITES] = 4; + pfile->counters[POSIX_SEEKS] = 0; + pfile->counters[POSIX_STATS] = 0; + pfile->counters[POSIX_MMAPS] = -1; + pfile->counters[POSIX_FSYNCS] = 0; + pfile->counters[POSIX_FDSYNCS] = 0; + pfile->counters[POSIX_RENAME_SOURCES] = 0; + pfile->counters[POSIX_RENAME_TARGETS] = 0; + pfile->counters[POSIX_RENAMED_FROM] = 0; + pfile->counters[POSIX_MODE] = 436; + pfile->counters[POSIX_BYTES_READ] = 67108864; + pfile->counters[POSIX_BYTES_WRITTEN] = 67108864; + pfile->counters[POSIX_MAX_BYTE_READ] = 67108863; + pfile->counters[POSIX_MAX_BYTE_WRITTEN] = 67108863; + pfile->counters[POSIX_CONSEC_READS] = 0; + pfile->counters[POSIX_CONSEC_WRITES] = 0; + pfile->counters[POSIX_SEQ_READS] = 3; + pfile->counters[POSIX_SEQ_WRITES] = 3; + pfile->counters[POSIX_RW_SWITCHES] = 4; + pfile->counters[POSIX_MEM_NOT_ALIGNED] = 0; + pfile->counters[POSIX_MEM_ALIGNMENT] = 8; + pfile->counters[POSIX_FILE_NOT_ALIGNED] = 0; + pfile->counters[POSIX_FILE_ALIGNMENT] = 4096; + pfile->counters[POSIX_MAX_READ_TIME_SIZE] = 16777216; + pfile->counters[POSIX_MAX_WRITE_TIME_SIZE] = 16777216; + pfile->counters[POSIX_SIZE_READ_0_100] = 0; + pfile->counters[POSIX_SIZE_READ_100_1K] = 0; + pfile->counters[POSIX_SIZE_READ_1K_10K] = 0; + pfile->counters[POSIX_SIZE_READ_10K_100K] = 0; + pfile->counters[POSIX_SIZE_READ_100K_1M] = 0; + pfile->counters[POSIX_SIZE_READ_1M_4M] = 0; + pfile->counters[POSIX_SIZE_READ_4M_10M] = 0; + pfile->counters[POSIX_SIZE_READ_10M_100M] = 4; + pfile->counters[POSIX_SIZE_READ_100M_1G] = 0; + pfile->counters[POSIX_SIZE_READ_1G_PLUS] = 0; + pfile->counters[POSIX_SIZE_WRITE_0_100] = 0; + pfile->counters[POSIX_SIZE_WRITE_100_1K] = 0; + pfile->counters[POSIX_SIZE_WRITE_1K_10K] = 0; + pfile->counters[POSIX_SIZE_WRITE_10K_100K] = 0; + pfile->counters[POSIX_SIZE_WRITE_100K_1M] = 0; + pfile->counters[POSIX_SIZE_WRITE_1M_4M] = 0; + pfile->counters[POSIX_SIZE_WRITE_4M_10M] = 0; + pfile->counters[POSIX_SIZE_WRITE_10M_100M] = 4; + pfile->counters[POSIX_SIZE_WRITE_100M_1G] = 0; + pfile->counters[POSIX_SIZE_WRITE_1G_PLUS] = 0; + pfile->counters[POSIX_STRIDE1_STRIDE] = 0; + pfile->counters[POSIX_STRIDE2_STRIDE] = 0; + pfile->counters[POSIX_STRIDE3_STRIDE] = 0; + pfile->counters[POSIX_STRIDE4_STRIDE] = 0; + pfile->counters[POSIX_STRIDE1_COUNT] = 0; + pfile->counters[POSIX_STRIDE2_COUNT] = 0; + pfile->counters[POSIX_STRIDE3_COUNT] = 0; + pfile->counters[POSIX_STRIDE4_COUNT] = 0; + pfile->counters[POSIX_ACCESS1_ACCESS] = 16777216; + pfile->counters[POSIX_ACCESS2_ACCESS] = 0; + pfile->counters[POSIX_ACCESS3_ACCESS] = 0; + pfile->counters[POSIX_ACCESS4_ACCESS] = 0; + pfile->counters[POSIX_ACCESS1_COUNT] = 8; + pfile->counters[POSIX_ACCESS2_COUNT] = 0; + pfile->counters[POSIX_ACCESS3_COUNT] = 0; + pfile->counters[POSIX_ACCESS4_COUNT] = 0; +#if 0 + pfile->counters[POSIX_FASTEST_RANK] = 2; + pfile->counters[POSIX_FASTEST_RANK_BYTES] = 33554432; + pfile->counters[POSIX_SLOWEST_RANK] = 3; + pfile->counters[POSIX_SLOWEST_RANK_BYTES] = 33554432; +#else + /* this is a non-shared record; we don't expect fastest and slowest + * fields to be set in this case */ + pfile->counters[POSIX_FASTEST_RANK] = 0; + pfile->counters[POSIX_FASTEST_RANK_BYTES] = 0; + pfile->counters[POSIX_SLOWEST_RANK] = 0; + pfile->counters[POSIX_SLOWEST_RANK_BYTES] = 0; +#endif + + pfile->fcounters[POSIX_F_OPEN_START_TIMESTAMP] = 0.008787; + pfile->fcounters[POSIX_F_READ_START_TIMESTAMP] = 0.079433; + pfile->fcounters[POSIX_F_WRITE_START_TIMESTAMP] = 0.009389; + pfile->fcounters[POSIX_F_CLOSE_START_TIMESTAMP] = 0.008901; + pfile->fcounters[POSIX_F_OPEN_END_TIMESTAMP] = 0.079599; + pfile->fcounters[POSIX_F_READ_END_TIMESTAMP] = 0.088423; + pfile->fcounters[POSIX_F_WRITE_END_TIMESTAMP] = 0.042157; + pfile->fcounters[POSIX_F_CLOSE_END_TIMESTAMP] = 0.088617; + pfile->fcounters[POSIX_F_READ_TIME] = 0.030387; + pfile->fcounters[POSIX_F_WRITE_TIME] = 0.082557; + pfile->fcounters[POSIX_F_META_TIME] = 0.000177; + pfile->fcounters[POSIX_F_MAX_READ_TIME] = 0.008990; + pfile->fcounters[POSIX_F_MAX_WRITE_TIME] = 0.032618; +#if 0 + pfile->fcounters[POSIX_F_FASTEST_RANK_TIME] = 0.015122; + pfile->fcounters[POSIX_F_SLOWEST_RANK_TIME] = 0.040990; +#else + /* this is a non-shared record; we don't expect fastest and slowest + * fields to be set in this case */ + pfile->fcounters[POSIX_F_FASTEST_RANK_TIME] = 0; + pfile->fcounters[POSIX_F_SLOWEST_RANK_TIME] = 0; +#endif + pfile->fcounters[POSIX_F_VARIANCE_RANK_TIME] = 0.000090; + pfile->fcounters[POSIX_F_VARIANCE_RANK_BYTES] = 0.000000; + + return; +} + +/* Set example values for record of type stdio. As elsewhere in the + * logutils API, the size of the buffer is implied. + */ +static void stdio_set_dummy_record(void* buffer) { + struct darshan_stdio_file* sfile = buffer; + + /* This function must be updated (or at least checked) if the stdio + * module log format changes + */ + munit_assert_int(DARSHAN_STDIO_VER, ==, 2); + + sfile->base_rec.id = 552491373643638544UL; + sfile->base_rec.rank = 0; + + sfile->counters[STDIO_OPENS] = 1; + sfile->counters[STDIO_FDOPENS] = 0; + sfile->counters[STDIO_READS] = 0; + sfile->counters[STDIO_WRITES] = 1; + sfile->counters[STDIO_SEEKS] = 0; + sfile->counters[STDIO_FLUSHES] = 0; + sfile->counters[STDIO_BYTES_WRITTEN] = 16777216; + sfile->counters[STDIO_BYTES_READ] = 0; + sfile->counters[STDIO_MAX_BYTE_READ] = 0; + sfile->counters[STDIO_MAX_BYTE_WRITTEN] = 16777215; + sfile->counters[STDIO_FASTEST_RANK] = 0; + sfile->counters[STDIO_FASTEST_RANK_BYTES] = 0; + sfile->counters[STDIO_SLOWEST_RANK] = 0; + sfile->counters[STDIO_SLOWEST_RANK_BYTES] = 0; + + sfile->fcounters[STDIO_F_META_TIME] = 0.000126; + sfile->fcounters[STDIO_F_WRITE_TIME] = 0.008228; + sfile->fcounters[STDIO_F_READ_TIME] = 0.000000; + sfile->fcounters[STDIO_F_OPEN_START_TIMESTAMP] = 0.043529; + sfile->fcounters[STDIO_F_CLOSE_START_TIMESTAMP] = 0.052128; + sfile->fcounters[STDIO_F_WRITE_START_TIMESTAMP] = 0.043750; + sfile->fcounters[STDIO_F_READ_START_TIMESTAMP] = 0.000000; + sfile->fcounters[STDIO_F_OPEN_END_TIMESTAMP] = 0.043636; + sfile->fcounters[STDIO_F_CLOSE_END_TIMESTAMP] = 0.052148; + sfile->fcounters[STDIO_F_WRITE_END_TIMESTAMP] = 0.051978; + sfile->fcounters[STDIO_F_READ_END_TIMESTAMP] = 0.000000; + sfile->fcounters[STDIO_F_FASTEST_RANK_TIME] = 0.000000; + sfile->fcounters[STDIO_F_SLOWEST_RANK_TIME] = 0.000000; + sfile->fcounters[STDIO_F_VARIANCE_RANK_TIME] = 0.000000; + sfile->fcounters[STDIO_F_VARIANCE_RANK_BYTES] = 0.000000; + + return; +} + +/* Set example values for record of type mpiio. As elsewhere in the + * logutils API, the size of the buffer is implied. + */ +static void mpiio_set_dummy_record(void* buffer) +{ + struct darshan_mpiio_file* mfile = buffer; + + /* This function must be updated (or at least checked) if the mpiio + * module log format changes + */ + munit_assert_int(DARSHAN_MPIIO_VER, ==, 3); + + mfile->base_rec.id = 15574190512568163195UL; + mfile->base_rec.rank = 0; + + mfile->counters[MPIIO_INDEP_OPENS] = 8; + mfile->counters[MPIIO_COLL_OPENS] = 0; + mfile->counters[MPIIO_INDEP_READS] = 4; + mfile->counters[MPIIO_INDEP_WRITES] = 4; + mfile->counters[MPIIO_COLL_READS] = 0; + mfile->counters[MPIIO_COLL_WRITES] = 0; + mfile->counters[MPIIO_SPLIT_READS] = 0; + mfile->counters[MPIIO_SPLIT_WRITES] = 0; + mfile->counters[MPIIO_NB_READS] = 0; + mfile->counters[MPIIO_NB_WRITES] = 0; + mfile->counters[MPIIO_SYNCS] = 0; + mfile->counters[MPIIO_HINTS] = 0; + mfile->counters[MPIIO_VIEWS] = 0; + mfile->counters[MPIIO_MODE] = 9; + mfile->counters[MPIIO_BYTES_READ] = 67108864; + mfile->counters[MPIIO_BYTES_WRITTEN] = 67108864; + mfile->counters[MPIIO_RW_SWITCHES] = 4; + mfile->counters[MPIIO_MAX_READ_TIME_SIZE] = 16777216; + mfile->counters[MPIIO_MAX_WRITE_TIME_SIZE] = 16777216; + mfile->counters[MPIIO_SIZE_READ_AGG_0_100] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_100_1K] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_1K_10K] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_10K_100K] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_100K_1M] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_1M_4M] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_4M_10M] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_10M_100M] = 4; + mfile->counters[MPIIO_SIZE_READ_AGG_100M_1G] = 0; + mfile->counters[MPIIO_SIZE_READ_AGG_1G_PLUS] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_0_100] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_100_1K] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_1K_10K] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_10K_100K] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_100K_1M] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_1M_4M] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_4M_10M] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_10M_100M] = 4; + mfile->counters[MPIIO_SIZE_WRITE_AGG_100M_1G] = 0; + mfile->counters[MPIIO_SIZE_WRITE_AGG_1G_PLUS] = 0; + mfile->counters[MPIIO_ACCESS1_ACCESS] = 16777216; + mfile->counters[MPIIO_ACCESS2_ACCESS] = 0; + mfile->counters[MPIIO_ACCESS3_ACCESS] = 0; + mfile->counters[MPIIO_ACCESS4_ACCESS] = 0; + mfile->counters[MPIIO_ACCESS1_COUNT] = 8; + mfile->counters[MPIIO_ACCESS2_COUNT] = 0; + mfile->counters[MPIIO_ACCESS3_COUNT] = 0; + mfile->counters[MPIIO_ACCESS4_COUNT] = 0; +#if 0 + mfile->counters[MPIIO_FASTEST_RANK] = 2; + mfile->counters[MPIIO_FASTEST_RANK_BYTES] = 33554432; + mfile->counters[MPIIO_SLOWEST_RANK] = 3; + mfile->counters[MPIIO_SLOWEST_RANK_BYTES] = 33554432; +#else + /* should not be set for single-rank records */ + mfile->counters[MPIIO_FASTEST_RANK] = 0; + mfile->counters[MPIIO_FASTEST_RANK_BYTES] = 0; + mfile->counters[MPIIO_SLOWEST_RANK] = 0; + mfile->counters[MPIIO_SLOWEST_RANK_BYTES] = 0; +#endif + + mfile->fcounters[MPIIO_F_OPEN_START_TIMESTAMP] = 0.006095; + mfile->fcounters[MPIIO_F_READ_START_TIMESTAMP] = 0.079428; + mfile->fcounters[MPIIO_F_WRITE_START_TIMESTAMP] = 0.009384; + mfile->fcounters[MPIIO_F_CLOSE_START_TIMESTAMP] = 0.018622; + mfile->fcounters[MPIIO_F_OPEN_END_TIMESTAMP] = 0.079620; + mfile->fcounters[MPIIO_F_READ_END_TIMESTAMP] = 0.088598; + mfile->fcounters[MPIIO_F_WRITE_END_TIMESTAMP] = 0.042630; + mfile->fcounters[MPIIO_F_CLOSE_END_TIMESTAMP] = 0.089300; + mfile->fcounters[MPIIO_F_READ_TIME] = 0.031077; + mfile->fcounters[MPIIO_F_WRITE_TIME] = 0.084623; + mfile->fcounters[MPIIO_F_META_TIME] = 0.020772; + mfile->fcounters[MPIIO_F_MAX_READ_TIME] = 0.009170; + mfile->fcounters[MPIIO_F_MAX_WRITE_TIME] = 0.033097; +#if 0 + mfile->fcounters[MPIIO_F_FASTEST_RANK_TIME] = 0.021213; + mfile->fcounters[MPIIO_F_SLOWEST_RANK_TIME] = 0.046796; + mfile->fcounters[MPIIO_F_VARIANCE_RANK_TIME] = 0.000087; + mfile->fcounters[MPIIO_F_VARIANCE_RANK_BYTES] = 0.000000; +#else + /* should not be set for single-rank records */ + mfile->fcounters[MPIIO_F_FASTEST_RANK_TIME] = 0; + mfile->fcounters[MPIIO_F_SLOWEST_RANK_TIME] = 0; + mfile->fcounters[MPIIO_F_VARIANCE_RANK_TIME] = 0; + mfile->fcounters[MPIIO_F_VARIANCE_RANK_BYTES] = 0; +#endif + + return; +} + +/* Validate that the aggregation produced sane values after being used to + * aggregate 2 rank records. If shared_file_flag, then the two records + * refer to the same file (but from different ranks). Otherwise the two + * records refer to different files. + */ +static void posix_validate_double_dummy_record(void* buffer, struct darshan_derived_metrics* metrics, int shared_file_flag) +{ + struct darshan_posix_file* pfile = buffer; + + /* This function must be updated (or at least checked) if the posix + * module log format changes + */ + munit_assert_int(DARSHAN_POSIX_VER, ==, 4); + + /* check base record */ + if(shared_file_flag) + munit_assert_int64(pfile->base_rec.id, ==, 15574190512568163195UL); + else + munit_assert_int64(pfile->base_rec.id, ==, 0); + munit_assert_int64(pfile->base_rec.rank, ==, -1); + + /* double */ + munit_assert_int64(pfile->counters[POSIX_OPENS], ==, 32); + /* stay set at -1 */ + munit_assert_int64(pfile->counters[POSIX_MMAPS], ==, -1); + /* stay set */ + munit_assert_int64(pfile->counters[POSIX_MODE], ==, 436); + + /* "fastest" behavior should change depending on if records are shared + * or not + */ + if(shared_file_flag) + /* it's a tie; either rank would be correct */ + munit_assert(pfile->counters[POSIX_FASTEST_RANK] == 0 || pfile->counters[POSIX_FASTEST_RANK] == 1); + else + munit_assert_int64(pfile->counters[POSIX_FASTEST_RANK], ==, -1); + + /* double */ + munit_assert_double_equal(pfile->fcounters[POSIX_F_READ_TIME], .060774, 6); + + /* variance should be cleared right now */ + munit_assert_int64(pfile->fcounters[POSIX_F_VARIANCE_RANK_TIME], ==, 0); + + /* check derived metrics */ + /* byte values should match regardless, just different values for count + * depending on if the records refer to a shared file or not + */ + if(shared_file_flag) + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].count, ==, 1); + else + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].count, ==, 2); + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].total_read_volume_bytes, ==, 134217728); + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].max_offset_bytes, ==, 67108863); + + /* check aggregate perf. Should be the same for both unique and + * "shared" records, since both cases have the same amount of data and + * time. This is a good apex value to check because it combines several + * other intermediate derived values into a final calculation. + */ + munit_assert_double_equal(metrics->agg_perf_by_slowest, 2263.063445, 6); + + return; +} + +/* Validate that the aggregation produced sane values after being used to + * aggregate 2 rank records. If shared_file_flag, then the two records + * refer to the same file (but from different ranks). Otherwise the two + * records refer to different files. + */ +static void stdio_validate_double_dummy_record(void* buffer, struct darshan_derived_metrics* metrics, int shared_file_flag) +{ + struct darshan_stdio_file* sfile = buffer; + + /* This function must be updated (or at least checked) if the stdio + * module log format changes + */ + munit_assert_int(DARSHAN_STDIO_VER, ==, 2); + + /* check base record */ + if(shared_file_flag) + munit_assert_int64(sfile->base_rec.id, ==, 552491373643638544UL); + else + munit_assert_int64(sfile->base_rec.id, ==, 0); + munit_assert_int64(sfile->base_rec.rank, ==, -1); + + /* double */ + munit_assert_int64(sfile->counters[STDIO_OPENS], ==, 2); + /* stay set */ + munit_assert_int64(sfile->counters[STDIO_MAX_BYTE_WRITTEN], ==, 16777215); + /* double */ + munit_assert_int64(sfile->counters[STDIO_BYTES_WRITTEN], ==, 33554432); + + /* "fastest" behavior should change depending on if records are shared + * or not + */ + if(shared_file_flag) + /* it's a tie; either rank would be correct */ + munit_assert(sfile->counters[STDIO_FASTEST_RANK] == 0 || sfile->counters[STDIO_FASTEST_RANK] == 1); + else + munit_assert_int64(sfile->counters[STDIO_FASTEST_RANK], ==, -1); + + /* double */ + munit_assert_double_equal(sfile->fcounters[STDIO_F_WRITE_TIME], .016456, 6); + + /* variance should be cleared right now */ + munit_assert_int64(sfile->fcounters[STDIO_F_VARIANCE_RANK_TIME], ==, 0); + + /* check derived metrics */ + /* byte values should match regardless, just different values for count + * depending on if the records refer to a shared file or not + */ + if(shared_file_flag) + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].count, ==, 1); + else + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].count, ==, 2); + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].total_write_volume_bytes, ==, 33554432); + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].max_offset_bytes, ==, 16777215); + + /* check aggregate perf. Should be the same for both unique and + * "shared" records, since both cases have the same amount of data and + * time. This is a good apex value to check because it combines several + * other intermediate derived values into a final calculation. + */ + munit_assert_double_equal(metrics->agg_perf_by_slowest, 3830.500359, 6); + + return; +} + +/* Validate that the aggregation produced sane values after being used to + * aggregate 2 rank records. If shared_file_flag, then the two records + * refer to the same file (but from different ranks). Otherwise the two + * records refer to different files. + */ +static void mpiio_validate_double_dummy_record(void* buffer, struct darshan_derived_metrics* metrics, int shared_file_flag) +{ + struct darshan_mpiio_file* mfile = buffer; + + /* This function must be updated (or at least checked) if the mpiio + * module log format changes + */ + munit_assert_int(DARSHAN_MPIIO_VER, ==, 3); + + /* check base record */ + if(shared_file_flag) + munit_assert_int64(mfile->base_rec.id, ==, 15574190512568163195UL); + else + munit_assert_int64(mfile->base_rec.id, ==, 0); + munit_assert_int64(mfile->base_rec.rank, ==, -1); + + /* double */ + munit_assert_int64(mfile->counters[MPIIO_INDEP_OPENS], ==, 16); + /* stay set */ + munit_assert_int64(mfile->counters[MPIIO_MODE], ==, 9); + /* double */ + munit_assert_int64(mfile->counters[MPIIO_BYTES_WRITTEN], ==, 134217728); + + /* "fastest" behavior should change depending on if records are shared + * or not + */ + if(shared_file_flag) + /* it's a tie; either rank would be correct */ + munit_assert(mfile->counters[MPIIO_FASTEST_RANK] == 0 || mfile->counters[MPIIO_FASTEST_RANK] == 1); + else + munit_assert_int64(mfile->counters[MPIIO_FASTEST_RANK], ==, -1); + + /* double */ + munit_assert_double_equal(mfile->fcounters[MPIIO_F_WRITE_TIME], .169246, 6); + + /* variance should be cleared right now */ + munit_assert_int64(mfile->fcounters[MPIIO_F_VARIANCE_RANK_TIME], ==, 0); + + /* check derived metrics */ + /* byte values should match regardless, just different values for count + * depending on if the records refer to a shared file or not + */ + if(shared_file_flag) + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].count, ==, 1); + else + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].count, ==, 2); + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].total_write_volume_bytes, ==, 134217728); + /* the mpiio module doesn't report max offsets */ + munit_assert_int64(metrics->category_counters[DARSHAN_ALL_FILES].max_offset_bytes, ==, -1); + + /* check aggregate perf. Should be the same for both unique and + * "shared" records, since both cases have the same amount of data and + * time. This is a good apex value to check because it combines several + * other intermediate derived values into a final calculation. + */ + munit_assert_double_equal(metrics->agg_perf_by_slowest, 1875.842663, 6); + + return; +} diff --git a/darshan-util/tests/unit-tests/munit/LICENSE b/darshan-util/tests/unit-tests/munit/LICENSE new file mode 100644 index 000000000..2991ac706 --- /dev/null +++ b/darshan-util/tests/unit-tests/munit/LICENSE @@ -0,0 +1,21 @@ +µnit Testing Framework +Copyright (c) 2013-2016 Evan Nemerson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/darshan-util/tests/unit-tests/munit/munit.c b/darshan-util/tests/unit-tests/munit/munit.c new file mode 100644 index 000000000..00ede07ca --- /dev/null +++ b/darshan-util/tests/unit-tests/munit/munit.c @@ -0,0 +1,2055 @@ +/* Copyright (c) 2013-2018 Evan Nemerson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/*** Configuration ***/ + +/* This is just where the output from the test goes. It's really just + * meant to let you choose stdout or stderr, but if anyone really want + * to direct it to a file let me know, it would be fairly easy to + * support. */ +#if !defined(MUNIT_OUTPUT_FILE) +# define MUNIT_OUTPUT_FILE stdout +#endif + +/* This is a bit more useful; it tells µnit how to format the seconds in + * timed tests. If your tests run for longer you might want to reduce + * it, and if your computer is really fast and your tests are tiny you + * can increase it. */ +#if !defined(MUNIT_TEST_TIME_FORMAT) +# define MUNIT_TEST_TIME_FORMAT "0.8f" +#endif + +/* If you have long test names you might want to consider bumping + * this. The result information takes 43 characters. */ +#if !defined(MUNIT_TEST_NAME_LEN) +# define MUNIT_TEST_NAME_LEN 37 +#endif + +/* If you don't like the timing information, you can disable it by + * defining MUNIT_DISABLE_TIMING. */ +#if !defined(MUNIT_DISABLE_TIMING) +# define MUNIT_ENABLE_TIMING +#endif + +/*** End configuration ***/ + +#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200809L) +# undef _POSIX_C_SOURCE +#endif +#if !defined(_POSIX_C_SOURCE) +# define _POSIX_C_SOURCE 200809L +#endif + +/* Solaris freaks out if you try to use a POSIX or SUS standard without + * the "right" C standard. */ +#if defined(_XOPEN_SOURCE) +# undef _XOPEN_SOURCE +#endif + +#if defined(__STDC_VERSION__) +# if __STDC_VERSION__ >= 201112L +# define _XOPEN_SOURCE 700 +# elif __STDC_VERSION__ >= 199901L +# define _XOPEN_SOURCE 600 +# endif +#endif + +/* Because, according to Microsoft, POSIX is deprecated. You've got + * to appreciate the chutzpah. */ +#if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE) +# define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# include +#elif defined(_WIN32) +/* https://msdn.microsoft.com/en-us/library/tf4dy80a.aspx */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(MUNIT_NO_NL_LANGINFO) && !defined(_WIN32) +#define MUNIT_NL_LANGINFO +#include +#include +#include +#endif + +#if !defined(_WIN32) +# include +# include +# include +#else +# include +# include +# include +# if !defined(STDERR_FILENO) +# define STDERR_FILENO _fileno(stderr) +# endif +#endif + +#include "munit.h" + +#define MUNIT_STRINGIFY(x) #x +#define MUNIT_XSTRINGIFY(x) MUNIT_STRINGIFY(x) + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) +# define MUNIT_THREAD_LOCAL __thread +#elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) || defined(_Thread_local) +# define MUNIT_THREAD_LOCAL _Thread_local +#elif defined(_WIN32) +# define MUNIT_THREAD_LOCAL __declspec(thread) +#endif + +/* MSVC 12.0 will emit a warning at /W4 for code like 'do { ... } + * while (0)', or 'do { ... } while (1)'. I'm pretty sure nobody + * at Microsoft compiles with /W4. */ +#if defined(_MSC_VER) && (_MSC_VER <= 1800) +#pragma warning(disable: 4127) +#endif + +#if defined(_WIN32) || defined(__EMSCRIPTEN__) +# define MUNIT_NO_FORK +#endif + +#if defined(__EMSCRIPTEN__) +# define MUNIT_NO_BUFFER +#endif + +/*** Logging ***/ + +static MunitLogLevel munit_log_level_visible = MUNIT_LOG_INFO; +static MunitLogLevel munit_log_level_fatal = MUNIT_LOG_ERROR; + +#if defined(MUNIT_THREAD_LOCAL) +static MUNIT_THREAD_LOCAL munit_bool munit_error_jmp_buf_valid = 0; +static MUNIT_THREAD_LOCAL jmp_buf munit_error_jmp_buf; +#endif + +/* At certain warning levels, mingw will trigger warnings about + * suggesting the format attribute, which we've explicity *not* set + * because it will then choke on our attempts to use the MS-specific + * I64 modifier for size_t (which we have to use since MSVC doesn't + * support the C99 z modifier). */ + +#if defined(__MINGW32__) || defined(__MINGW64__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsuggest-attribute=format" +#endif + +MUNIT_PRINTF(5,0) +static void +munit_logf_exv(MunitLogLevel level, FILE* fp, const char* filename, int line, const char* format, va_list ap) { + if (level < munit_log_level_visible) + return; + + switch (level) { + case MUNIT_LOG_DEBUG: + fputs("Debug", fp); + break; + case MUNIT_LOG_INFO: + fputs("Info", fp); + break; + case MUNIT_LOG_WARNING: + fputs("Warning", fp); + break; + case MUNIT_LOG_ERROR: + fputs("Error", fp); + break; + default: + munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Invalid log level (%d)", level); + return; + } + + fputs(": ", fp); + if (filename != NULL) + fprintf(fp, "%s:%d: ", filename, line); + vfprintf(fp, format, ap); + fputc('\n', fp); +} + +MUNIT_PRINTF(3,4) +static void +munit_logf_internal(MunitLogLevel level, FILE* fp, const char* format, ...) { + va_list ap; + + va_start(ap, format); + munit_logf_exv(level, fp, NULL, 0, format, ap); + va_end(ap); +} + +static void +munit_log_internal(MunitLogLevel level, FILE* fp, const char* message) { + munit_logf_internal(level, fp, "%s", message); +} + +void +munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...) { + va_list ap; + + va_start(ap, format); + munit_logf_exv(level, stderr, filename, line, format, ap); + va_end(ap); + + if (level >= munit_log_level_fatal) { +#if defined(MUNIT_THREAD_LOCAL) + if (munit_error_jmp_buf_valid) + longjmp(munit_error_jmp_buf, 1); +#endif + abort(); + } +} + +void +munit_errorf_ex(const char* filename, int line, const char* format, ...) { + va_list ap; + + va_start(ap, format); + munit_logf_exv(MUNIT_LOG_ERROR, stderr, filename, line, format, ap); + va_end(ap); + +#if defined(MUNIT_THREAD_LOCAL) + if (munit_error_jmp_buf_valid) + longjmp(munit_error_jmp_buf, 1); +#endif + abort(); +} + +#if defined(__MINGW32__) || defined(__MINGW64__) +#pragma GCC diagnostic pop +#endif + +#if !defined(MUNIT_STRERROR_LEN) +# define MUNIT_STRERROR_LEN 80 +#endif + +static void +munit_log_errno(MunitLogLevel level, FILE* fp, const char* msg) { +#if defined(MUNIT_NO_STRERROR_R) || (defined(__MINGW32__) && !defined(MINGW_HAS_SECURE_API)) + munit_logf_internal(level, fp, "%s: %s (%d)", msg, strerror(errno), errno); +#else + char munit_error_str[MUNIT_STRERROR_LEN]; + munit_error_str[0] = '\0'; + +#if !defined(_WIN32) + strerror_r(errno, munit_error_str, MUNIT_STRERROR_LEN); +#else + strerror_s(munit_error_str, MUNIT_STRERROR_LEN, errno); +#endif + + munit_logf_internal(level, fp, "%s: %s (%d)", msg, munit_error_str, errno); +#endif +} + +/*** Memory allocation ***/ + +void* +munit_malloc_ex(const char* filename, int line, size_t size) { + void* ptr; + + if (size == 0) + return NULL; + + ptr = calloc(1, size); + if (MUNIT_UNLIKELY(ptr == NULL)) { + munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Failed to allocate %" MUNIT_SIZE_MODIFIER "u bytes.", size); + } + + return ptr; +} + +/*** Timer code ***/ + +#if defined(MUNIT_ENABLE_TIMING) + +#define psnip_uint64_t munit_uint64_t +#define psnip_uint32_t munit_uint32_t + +/* Code copied from portable-snippets + * . If you need to + * change something, please do it there so we can keep the code in + * sync. */ + +/* Clocks (v1) + * Portable Snippets - https://gitub.com/nemequ/portable-snippets + * Created by Evan Nemerson + * + * To the extent possible under law, the authors have waived all + * copyright and related or neighboring rights to this code. For + * details, see the Creative Commons Zero 1.0 Universal license at + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +#if !defined(PSNIP_CLOCK_H) +#define PSNIP_CLOCK_H + +#if !defined(psnip_uint64_t) +# include "../exact-int/exact-int.h" +#endif + +#if !defined(PSNIP_CLOCK_STATIC_INLINE) +# if defined(__GNUC__) +# define PSNIP_CLOCK__COMPILER_ATTRIBUTES __attribute__((__unused__)) +# else +# define PSNIP_CLOCK__COMPILER_ATTRIBUTES +# endif + +# define PSNIP_CLOCK__FUNCTION PSNIP_CLOCK__COMPILER_ATTRIBUTES static +#endif + +enum PsnipClockType { + /* This clock provides the current time, in units since 1970-01-01 + * 00:00:00 UTC not including leap seconds. In other words, UNIX + * time. Keep in mind that this clock doesn't account for leap + * seconds, and can go backwards (think NTP adjustments). */ + PSNIP_CLOCK_TYPE_WALL = 1, + /* The CPU time is a clock which increases only when the current + * process is active (i.e., it doesn't increment while blocking on + * I/O). */ + PSNIP_CLOCK_TYPE_CPU = 2, + /* Monotonic time is always running (unlike CPU time), but it only + ever moves forward unless you reboot the system. Things like NTP + adjustments have no effect on this clock. */ + PSNIP_CLOCK_TYPE_MONOTONIC = 3 +}; + +struct PsnipClockTimespec { + psnip_uint64_t seconds; + psnip_uint64_t nanoseconds; +}; + +/* Methods we support: */ + +#define PSNIP_CLOCK_METHOD_CLOCK_GETTIME 1 +#define PSNIP_CLOCK_METHOD_TIME 2 +#define PSNIP_CLOCK_METHOD_GETTIMEOFDAY 3 +#define PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER 4 +#define PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME 5 +#define PSNIP_CLOCK_METHOD_CLOCK 6 +#define PSNIP_CLOCK_METHOD_GETPROCESSTIMES 7 +#define PSNIP_CLOCK_METHOD_GETRUSAGE 8 +#define PSNIP_CLOCK_METHOD_GETSYSTEMTIMEPRECISEASFILETIME 9 +#define PSNIP_CLOCK_METHOD_GETTICKCOUNT64 10 + +#include + +#if defined(HEDLEY_UNREACHABLE) +# define PSNIP_CLOCK_UNREACHABLE() HEDLEY_UNREACHABLE() +#else +# define PSNIP_CLOCK_UNREACHABLE() assert(0) +#endif + +/* Choose an implementation */ + +/* #undef PSNIP_CLOCK_WALL_METHOD */ +/* #undef PSNIP_CLOCK_CPU_METHOD */ +/* #undef PSNIP_CLOCK_MONOTONIC_METHOD */ + +/* We want to be able to detect the libc implementation, so we include + ( isn't available everywhere). */ + +#if defined(__unix__) || defined(__unix) || defined(__linux__) +# include +# include +#endif + +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) +/* These are known to work without librt. If you know of others + * please let us know so we can add them. */ +# if \ + (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))) || \ + (defined(__FreeBSD__)) +# define PSNIP_CLOCK_HAVE_CLOCK_GETTIME +# elif !defined(PSNIP_CLOCK_NO_LIBRT) +# define PSNIP_CLOCK_HAVE_CLOCK_GETTIME +# endif +#endif + +#if defined(_WIN32) +# if !defined(PSNIP_CLOCK_CPU_METHOD) +# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_GETPROCESSTIMES +# endif +# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) +# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER +# endif +#endif + +#if defined(__MACH__) && !defined(__gnu_hurd__) +# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) +# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME +# endif +#endif + +#if defined(PSNIP_CLOCK_HAVE_CLOCK_GETTIME) +# include +# if !defined(PSNIP_CLOCK_WALL_METHOD) +# if defined(CLOCK_REALTIME_PRECISE) +# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME_PRECISE +# elif !defined(__sun) +# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME +# endif +# endif +# if !defined(PSNIP_CLOCK_CPU_METHOD) +# if defined(_POSIX_CPUTIME) || defined(CLOCK_PROCESS_CPUTIME_ID) +# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_PROCESS_CPUTIME_ID +# elif defined(CLOCK_VIRTUAL) +# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_VIRTUAL +# endif +# endif +# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) +# if defined(CLOCK_MONOTONIC_RAW) +# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC +# elif defined(CLOCK_MONOTONIC_PRECISE) +# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC_PRECISE +# elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC) +# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC +# endif +# endif +#endif + +#if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L) +# if !defined(PSNIP_CLOCK_WALL_METHOD) +# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_GETTIMEOFDAY +# endif +#endif + +#if !defined(PSNIP_CLOCK_WALL_METHOD) +# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_TIME +#endif + +#if !defined(PSNIP_CLOCK_CPU_METHOD) +# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK +#endif + +/* Primarily here for testing. */ +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) && defined(PSNIP_CLOCK_REQUIRE_MONOTONIC) +# error No monotonic clock found. +#endif + +/* Implementations */ + +#if \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_TIME)) +# include +#endif + +#if \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) +# include +#endif + +#if \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) +# include +#endif + +#if \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) +# include +# include +#endif + +#if \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) +# include +# include +# include +#endif + +/*** Implementations ***/ + +#define PSNIP_CLOCK_NSEC_PER_SEC ((psnip_uint32_t) (1000000000ULL)) + +#if \ + (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock__clock_getres (clockid_t clk_id) { + struct timespec res; + int r; + + r = clock_getres(clk_id, &res); + if (r != 0) + return 0; + + return (psnip_uint32_t) (PSNIP_CLOCK_NSEC_PER_SEC / res.tv_nsec); +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock__clock_gettime (clockid_t clk_id, struct PsnipClockTimespec* res) { + struct timespec ts; + + if (clock_gettime(clk_id, &ts) != 0) + return -10; + + res->seconds = (psnip_uint64_t) (ts.tv_sec); + res->nanoseconds = (psnip_uint64_t) (ts.tv_nsec); + + return 0; +} +#endif + +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_wall_get_precision (void) { +#if !defined(PSNIP_CLOCK_WALL_METHOD) + return 0; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_WALL); +#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY + return 1000000; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME + return 1; +#else + return 0; +#endif +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock_wall_get_time (struct PsnipClockTimespec* res) { + (void) res; + +#if !defined(PSNIP_CLOCK_WALL_METHOD) + return -2; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_WALL, res); +#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME + res->seconds = time(NULL); + res->nanoseconds = 0; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) + return -6; + + res->seconds = tv.tv_sec; + res->nanoseconds = tv.tv_usec * 1000; +#else + return -2; +#endif + + return 0; +} + +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_cpu_get_precision (void) { +#if !defined(PSNIP_CLOCK_CPU_METHOD) + return 0; +#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_CPU); +#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK + return CLOCKS_PER_SEC; +#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES + return PSNIP_CLOCK_NSEC_PER_SEC / 100; +#else + return 0; +#endif +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock_cpu_get_time (struct PsnipClockTimespec* res) { +#if !defined(PSNIP_CLOCK_CPU_METHOD) + (void) res; + return -2; +#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_CPU, res); +#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK + clock_t t = clock(); + if (t == ((clock_t) -1)) + return -5; + res->seconds = t / CLOCKS_PER_SEC; + res->nanoseconds = (t % CLOCKS_PER_SEC) * (PSNIP_CLOCK_NSEC_PER_SEC / CLOCKS_PER_SEC); +#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES + FILETIME CreationTime, ExitTime, KernelTime, UserTime; + LARGE_INTEGER date, adjust; + + if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime)) + return -7; + + /* http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ */ + date.HighPart = UserTime.dwHighDateTime; + date.LowPart = UserTime.dwLowDateTime; + adjust.QuadPart = 11644473600000 * 10000; + date.QuadPart -= adjust.QuadPart; + + res->seconds = date.QuadPart / 10000000; + res->nanoseconds = (date.QuadPart % 10000000) * (PSNIP_CLOCK_NSEC_PER_SEC / 100); +#elif PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) != 0) + return -8; + + res->seconds = usage.ru_utime.tv_sec; + res->nanoseconds = tv.tv_usec * 1000; +#else + (void) res; + return -2; +#endif + + return 0; +} + +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_monotonic_get_precision (void) { +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) + return 0; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC); +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME + static mach_timebase_info_data_t tbi = { 0, }; + if (tbi.denom == 0) + mach_timebase_info(&tbi); + return (psnip_uint32_t) (tbi.numer / tbi.denom); +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 + return 1000; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER + LARGE_INTEGER Frequency; + QueryPerformanceFrequency(&Frequency); + return (psnip_uint32_t) ((Frequency.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) ? PSNIP_CLOCK_NSEC_PER_SEC : Frequency.QuadPart); +#else + return 0; +#endif +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock_monotonic_get_time (struct PsnipClockTimespec* res) { +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) + (void) res; + return -2; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC, res); +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME + psnip_uint64_t nsec = mach_absolute_time(); + static mach_timebase_info_data_t tbi = { 0, }; + if (tbi.denom == 0) + mach_timebase_info(&tbi); + nsec *= ((psnip_uint64_t) tbi.numer) / ((psnip_uint64_t) tbi.denom); + res->seconds = nsec / PSNIP_CLOCK_NSEC_PER_SEC; + res->nanoseconds = nsec % PSNIP_CLOCK_NSEC_PER_SEC; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER + LARGE_INTEGER t, f; + if (QueryPerformanceCounter(&t) == 0) + return -12; + + QueryPerformanceFrequency(&f); + res->seconds = t.QuadPart / f.QuadPart; + res->nanoseconds = t.QuadPart % f.QuadPart; + if (f.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) + res->nanoseconds /= f.QuadPart / PSNIP_CLOCK_NSEC_PER_SEC; + else + res->nanoseconds *= PSNIP_CLOCK_NSEC_PER_SEC / f.QuadPart; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 + const ULONGLONG msec = GetTickCount64(); + res->seconds = msec / 1000; + res->nanoseconds = sec % 1000; +#else + return -2; +#endif + + return 0; +} + +/* Returns the number of ticks per second for the specified clock. + * For example, a clock with millisecond precision would return 1000, + * and a clock with 1 second (such as the time() function) would + * return 1. + * + * If the requested clock isn't available, it will return 0. + * Hopefully this will be rare, but if it happens to you please let us + * know so we can work on finding a way to support your system. + * + * Note that different clocks on the same system often have a + * different precisions. + */ +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_get_precision (enum PsnipClockType clock_type) { + switch (clock_type) { + case PSNIP_CLOCK_TYPE_MONOTONIC: + return psnip_clock_monotonic_get_precision (); + case PSNIP_CLOCK_TYPE_CPU: + return psnip_clock_cpu_get_precision (); + case PSNIP_CLOCK_TYPE_WALL: + return psnip_clock_wall_get_precision (); + } + + PSNIP_CLOCK_UNREACHABLE(); + return 0; +} + +/* Set the provided timespec to the requested time. Returns 0 on + * success, or a negative value on failure. */ +PSNIP_CLOCK__FUNCTION int +psnip_clock_get_time (enum PsnipClockType clock_type, struct PsnipClockTimespec* res) { + assert(res != NULL); + + switch (clock_type) { + case PSNIP_CLOCK_TYPE_MONOTONIC: + return psnip_clock_monotonic_get_time (res); + case PSNIP_CLOCK_TYPE_CPU: + return psnip_clock_cpu_get_time (res); + case PSNIP_CLOCK_TYPE_WALL: + return psnip_clock_wall_get_time (res); + } + + return -1; +} + +#endif /* !defined(PSNIP_CLOCK_H) */ + +static psnip_uint64_t +munit_clock_get_elapsed(struct PsnipClockTimespec* start, struct PsnipClockTimespec* end) { + psnip_uint64_t r = (end->seconds - start->seconds) * PSNIP_CLOCK_NSEC_PER_SEC; + if (end->nanoseconds < start->nanoseconds) { + r -= (start->nanoseconds - end->nanoseconds); + } else { + r += (end->nanoseconds - start->nanoseconds); + } + return r; +} + +#else +# include +#endif /* defined(MUNIT_ENABLE_TIMING) */ + +/*** PRNG stuff ***/ + +/* This is (unless I screwed up, which is entirely possible) the + * version of PCG with 32-bit state. It was chosen because it has a + * small enough state that we should reliably be able to use CAS + * instead of requiring a lock for thread-safety. + * + * If I did screw up, I probably will not bother changing it unless + * there is a significant bias. It's really not important this be + * particularly strong, as long as it is fairly random it's much more + * important that it be reproducible, so bug reports have a better + * chance of being reproducible. */ + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) && !defined(__EMSCRIPTEN__) && (!defined(__GNUC_MINOR__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8)) +# define HAVE_STDATOMIC +#elif defined(__clang__) +# if __has_extension(c_atomic) +# define HAVE_CLANG_ATOMICS +# endif +#endif + +/* Workaround for http://llvm.org/bugs/show_bug.cgi?id=26911 */ +#if defined(__clang__) && defined(_WIN32) +# undef HAVE_STDATOMIC +# if defined(__c2__) +# undef HAVE_CLANG_ATOMICS +# endif +#endif + +#if defined(_OPENMP) +# define ATOMIC_UINT32_T uint32_t +# define ATOMIC_UINT32_INIT(x) (x) +#elif defined(HAVE_STDATOMIC) +# include +# define ATOMIC_UINT32_T _Atomic uint32_t +# define ATOMIC_UINT32_INIT(x) ATOMIC_VAR_INIT(x) +#elif defined(HAVE_CLANG_ATOMICS) +# define ATOMIC_UINT32_T _Atomic uint32_t +# define ATOMIC_UINT32_INIT(x) (x) +#elif defined(_WIN32) +# define ATOMIC_UINT32_T volatile LONG +# define ATOMIC_UINT32_INIT(x) (x) +#else +# define ATOMIC_UINT32_T volatile uint32_t +# define ATOMIC_UINT32_INIT(x) (x) +#endif + +static ATOMIC_UINT32_T munit_rand_state = ATOMIC_UINT32_INIT(42); + +#if defined(_OPENMP) +static inline void +munit_atomic_store(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T value) { +#pragma omp critical (munit_atomics) + *dest = value; +} + +static inline uint32_t +munit_atomic_load(ATOMIC_UINT32_T* src) { + int ret; +#pragma omp critical (munit_atomics) + ret = *src; + return ret; +} + +static inline uint32_t +munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) { + munit_bool ret; + +#pragma omp critical (munit_atomics) + { + if (*dest == *expected) { + *dest = desired; + ret = 1; + } else { + ret = 0; + } + } + + return ret; +} +#elif defined(HAVE_STDATOMIC) +# define munit_atomic_store(dest, value) atomic_store(dest, value) +# define munit_atomic_load(src) atomic_load(src) +# define munit_atomic_cas(dest, expected, value) atomic_compare_exchange_weak(dest, expected, value) +#elif defined(HAVE_CLANG_ATOMICS) +# define munit_atomic_store(dest, value) __c11_atomic_store(dest, value, __ATOMIC_SEQ_CST) +# define munit_atomic_load(src) __c11_atomic_load(src, __ATOMIC_SEQ_CST) +# define munit_atomic_cas(dest, expected, value) __c11_atomic_compare_exchange_weak(dest, expected, value, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#elif defined(__GNUC__) && (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) +# define munit_atomic_store(dest, value) __atomic_store_n(dest, value, __ATOMIC_SEQ_CST) +# define munit_atomic_load(src) __atomic_load_n(src, __ATOMIC_SEQ_CST) +# define munit_atomic_cas(dest, expected, value) __atomic_compare_exchange_n(dest, expected, value, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +# define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0) +# define munit_atomic_load(src) (*(src)) +# define munit_atomic_cas(dest, expected, value) __sync_bool_compare_and_swap(dest, *expected, value) +#elif defined(_WIN32) /* Untested */ +# define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0) +# define munit_atomic_load(src) (*(src)) +# define munit_atomic_cas(dest, expected, value) InterlockedCompareExchange((dest), (value), *(expected)) +#else +# warning No atomic implementation, PRNG will not be thread-safe +# define munit_atomic_store(dest, value) do { *(dest) = (value); } while (0) +# define munit_atomic_load(src) (*(src)) +static inline munit_bool +munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) { + if (*dest == *expected) { + *dest = desired; + return 1; + } else { + return 0; + } +} +#endif + +#define MUNIT_PRNG_MULTIPLIER (747796405U) +#define MUNIT_PRNG_INCREMENT (1729U) + +static munit_uint32_t +munit_rand_next_state(munit_uint32_t state) { + return state * MUNIT_PRNG_MULTIPLIER + MUNIT_PRNG_INCREMENT; +} + +static munit_uint32_t +munit_rand_from_state(munit_uint32_t state) { + munit_uint32_t res = ((state >> ((state >> 28) + 4)) ^ state) * (277803737U); + res ^= res >> 22; + return res; +} + +void +munit_rand_seed(munit_uint32_t seed) { + munit_uint32_t state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); + munit_atomic_store(&munit_rand_state, state); +} + +static munit_uint32_t +munit_rand_generate_seed(void) { + munit_uint32_t seed, state; +#if defined(MUNIT_ENABLE_TIMING) + struct PsnipClockTimespec wc = { 0, }; + + psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wc); + seed = (munit_uint32_t) wc.nanoseconds; +#else + seed = (munit_uint32_t) time(NULL); +#endif + + state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); + return munit_rand_from_state(state); +} + +static munit_uint32_t +munit_rand_state_uint32(munit_uint32_t* state) { + const munit_uint32_t old = *state; + *state = munit_rand_next_state(old); + return munit_rand_from_state(old); +} + +munit_uint32_t +munit_rand_uint32(void) { + munit_uint32_t old, state; + + do { + old = munit_atomic_load(&munit_rand_state); + state = munit_rand_next_state(old); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); + + return munit_rand_from_state(old); +} + +static void +munit_rand_state_memory(munit_uint32_t* state, size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) { + size_t members_remaining = size / sizeof(munit_uint32_t); + size_t bytes_remaining = size % sizeof(munit_uint32_t); + munit_uint8_t* b = data; + munit_uint32_t rv; + while (members_remaining-- > 0) { + rv = munit_rand_state_uint32(state); + memcpy(b, &rv, sizeof(munit_uint32_t)); + b += sizeof(munit_uint32_t); + } + if (bytes_remaining != 0) { + rv = munit_rand_state_uint32(state); + memcpy(b, &rv, bytes_remaining); + } +} + +void +munit_rand_memory(size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) { + munit_uint32_t old, state; + + do { + state = old = munit_atomic_load(&munit_rand_state); + munit_rand_state_memory(&state, size, data); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); +} + +static munit_uint32_t +munit_rand_state_at_most(munit_uint32_t* state, munit_uint32_t salt, munit_uint32_t max) { + /* We want (UINT32_MAX + 1) % max, which in unsigned arithmetic is the same + * as (UINT32_MAX + 1 - max) % max = -max % max. We compute -max using not + * to avoid compiler warnings. + */ + const munit_uint32_t min = (~max + 1U) % max; + munit_uint32_t x; + + if (max == (~((munit_uint32_t) 0U))) + return munit_rand_state_uint32(state) ^ salt; + + max++; + + do { + x = munit_rand_state_uint32(state) ^ salt; + } while (x < min); + + return x % max; +} + +static munit_uint32_t +munit_rand_at_most(munit_uint32_t salt, munit_uint32_t max) { + munit_uint32_t old, state; + munit_uint32_t retval; + + do { + state = old = munit_atomic_load(&munit_rand_state); + retval = munit_rand_state_at_most(&state, salt, max); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); + + return retval; +} + +int +munit_rand_int_range(int min, int max) { + munit_uint64_t range = (munit_uint64_t) max - (munit_uint64_t) min; + + if (min > max) + return munit_rand_int_range(max, min); + + if (range > (~((munit_uint32_t) 0U))) + range = (~((munit_uint32_t) 0U)); + + return min + munit_rand_at_most(0, (munit_uint32_t) range); +} + +double +munit_rand_double(void) { + munit_uint32_t old, state; + double retval = 0.0; + + do { + state = old = munit_atomic_load(&munit_rand_state); + + /* See http://mumble.net/~campbell/tmp/random_real.c for how to do + * this right. Patches welcome if you feel that this is too + * biased. */ + retval = munit_rand_state_uint32(&state) / ((~((munit_uint32_t) 0U)) + 1.0); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); + + return retval; +} + +/*** Test suite handling ***/ + +typedef struct { + unsigned int successful; + unsigned int skipped; + unsigned int failed; + unsigned int errored; +#if defined(MUNIT_ENABLE_TIMING) + munit_uint64_t cpu_clock; + munit_uint64_t wall_clock; +#endif +} MunitReport; + +typedef struct { + const char* prefix; + const MunitSuite* suite; + const char** tests; + munit_uint32_t seed; + unsigned int iterations; + MunitParameter* parameters; + munit_bool single_parameter_mode; + void* user_data; + MunitReport report; + munit_bool colorize; + munit_bool fork; + munit_bool show_stderr; + munit_bool fatal_failures; +} MunitTestRunner; + +const char* +munit_parameters_get(const MunitParameter params[], const char* key) { + const MunitParameter* param; + + for (param = params ; param != NULL && param->name != NULL ; param++) + if (strcmp(param->name, key) == 0) + return param->value; + return NULL; +} + +#if defined(MUNIT_ENABLE_TIMING) +static void +munit_print_time(FILE* fp, munit_uint64_t nanoseconds) { + fprintf(fp, "%" MUNIT_TEST_TIME_FORMAT, ((double) nanoseconds) / ((double) PSNIP_CLOCK_NSEC_PER_SEC)); +} +#endif + +/* Add a paramter to an array of parameters. */ +static MunitResult +munit_parameters_add(size_t* params_size, MunitParameter* params[MUNIT_ARRAY_PARAM(*params_size)], char* name, char* value) { + *params = realloc(*params, sizeof(MunitParameter) * (*params_size + 2)); + if (*params == NULL) + return MUNIT_ERROR; + + (*params)[*params_size].name = name; + (*params)[*params_size].value = value; + (*params_size)++; + (*params)[*params_size].name = NULL; + (*params)[*params_size].value = NULL; + + return MUNIT_OK; +} + +/* Concatenate two strings, but just return one of the components + * unaltered if the other is NULL or "". */ +static char* +munit_maybe_concat(size_t* len, char* prefix, char* suffix) { + char* res; + size_t res_l; + const size_t prefix_l = prefix != NULL ? strlen(prefix) : 0; + const size_t suffix_l = suffix != NULL ? strlen(suffix) : 0; + if (prefix_l == 0 && suffix_l == 0) { + res = NULL; + res_l = 0; + } else if (prefix_l == 0 && suffix_l != 0) { + res = suffix; + res_l = suffix_l; + } else if (prefix_l != 0 && suffix_l == 0) { + res = prefix; + res_l = prefix_l; + } else { + res_l = prefix_l + suffix_l; + res = malloc(res_l + 1); + memcpy(res, prefix, prefix_l); + memcpy(res + prefix_l, suffix, suffix_l); + res[res_l] = 0; + } + + if (len != NULL) + *len = res_l; + + return res; +} + +/* Possbily free a string returned by munit_maybe_concat. */ +static void +munit_maybe_free_concat(char* s, const char* prefix, const char* suffix) { + if (prefix != s && suffix != s) + free(s); +} + +/* Cheap string hash function, just used to salt the PRNG. */ +static munit_uint32_t +munit_str_hash(const char* name) { + const char *p; + munit_uint32_t h = 5381U; + + for (p = name; *p != '\0'; p++) + h = (h << 5) + h + *p; + + return h; +} + +static void +munit_splice(int from, int to) { + munit_uint8_t buf[1024]; +#if !defined(_WIN32) + ssize_t len; + ssize_t bytes_written; + ssize_t write_res; +#else + int len; + int bytes_written; + int write_res; +#endif + do { + len = read(from, buf, sizeof(buf)); + if (len > 0) { + bytes_written = 0; + do { + write_res = write(to, buf + bytes_written, len - bytes_written); + if (write_res < 0) + break; + bytes_written += write_res; + } while (bytes_written < len); + } + else + break; + } while (1); +} + +/* This is the part that should be handled in the child process */ +static MunitResult +munit_test_runner_exec(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[], MunitReport* report) { + unsigned int iterations = runner->iterations; + MunitResult result = MUNIT_FAIL; +#if defined(MUNIT_ENABLE_TIMING) + struct PsnipClockTimespec wall_clock_begin = { 0, }, wall_clock_end = { 0, }; + struct PsnipClockTimespec cpu_clock_begin = { 0, }, cpu_clock_end = { 0, }; +#endif + unsigned int i = 0; + + if ((test->options & MUNIT_TEST_OPTION_SINGLE_ITERATION) == MUNIT_TEST_OPTION_SINGLE_ITERATION) + iterations = 1; + else if (iterations == 0) + iterations = runner->suite->iterations; + + munit_rand_seed(runner->seed); + + do { + void* data = (test->setup == NULL) ? runner->user_data : test->setup(params, runner->user_data); + +#if defined(MUNIT_ENABLE_TIMING) + psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_begin); + psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_begin); +#endif + + result = test->test(params, data); + +#if defined(MUNIT_ENABLE_TIMING) + psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_end); + psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_end); +#endif + + if (test->tear_down != NULL) + test->tear_down(data); + + if (MUNIT_LIKELY(result == MUNIT_OK)) { + report->successful++; +#if defined(MUNIT_ENABLE_TIMING) + report->wall_clock += munit_clock_get_elapsed(&wall_clock_begin, &wall_clock_end); + report->cpu_clock += munit_clock_get_elapsed(&cpu_clock_begin, &cpu_clock_end); +#endif + } else { + switch ((int) result) { + case MUNIT_SKIP: + report->skipped++; + break; + case MUNIT_FAIL: + report->failed++; + break; + case MUNIT_ERROR: + report->errored++; + break; + default: + break; + } + break; + } + } while (++i < iterations); + + return result; +} + +#if defined(MUNIT_EMOTICON) +# define MUNIT_RESULT_STRING_OK ":)" +# define MUNIT_RESULT_STRING_SKIP ":|" +# define MUNIT_RESULT_STRING_FAIL ":(" +# define MUNIT_RESULT_STRING_ERROR ":o" +# define MUNIT_RESULT_STRING_TODO ":/" +#else +# define MUNIT_RESULT_STRING_OK "OK " +# define MUNIT_RESULT_STRING_SKIP "SKIP " +# define MUNIT_RESULT_STRING_FAIL "FAIL " +# define MUNIT_RESULT_STRING_ERROR "ERROR" +# define MUNIT_RESULT_STRING_TODO "TODO " +#endif + +static void +munit_test_runner_print_color(const MunitTestRunner* runner, const char* string, char color) { + if (runner->colorize) + fprintf(MUNIT_OUTPUT_FILE, "\x1b[3%cm%s\x1b[39m", color, string); + else + fputs(string, MUNIT_OUTPUT_FILE); +} + +#if !defined(MUNIT_NO_BUFFER) +static int +munit_replace_stderr(FILE* stderr_buf) { + if (stderr_buf != NULL) { + const int orig_stderr = dup(STDERR_FILENO); + + int errfd = fileno(stderr_buf); + if (MUNIT_UNLIKELY(errfd == -1)) { + exit(EXIT_FAILURE); + } + + dup2(errfd, STDERR_FILENO); + + return orig_stderr; + } + + return -1; +} + +static void +munit_restore_stderr(int orig_stderr) { + if (orig_stderr != -1) { + dup2(orig_stderr, STDERR_FILENO); + close(orig_stderr); + } +} +#endif /* !defined(MUNIT_NO_BUFFER) */ + +/* Run a test with the specified parameters. */ +static void +munit_test_runner_run_test_with_params(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[]) { + MunitResult result = MUNIT_OK; + MunitReport report = { + 0, 0, 0, 0, +#if defined(MUNIT_ENABLE_TIMING) + 0, 0 +#endif + }; + unsigned int output_l; + munit_bool first; + const MunitParameter* param; + FILE* stderr_buf; +#if !defined(MUNIT_NO_FORK) + int pipefd[2]; + pid_t fork_pid; + int orig_stderr; + ssize_t bytes_written = 0; + ssize_t write_res; + ssize_t bytes_read = 0; + ssize_t read_res; + int status = 0; + pid_t changed_pid; +#endif + + if (params != NULL) { + output_l = 2; + fputs(" ", MUNIT_OUTPUT_FILE); + first = 1; + for (param = params ; param != NULL && param->name != NULL ; param++) { + if (!first) { + fputs(", ", MUNIT_OUTPUT_FILE); + output_l += 2; + } else { + first = 0; + } + + output_l += fprintf(MUNIT_OUTPUT_FILE, "%s=%s", param->name, param->value); + } + while (output_l++ < MUNIT_TEST_NAME_LEN) { + fputc(' ', MUNIT_OUTPUT_FILE); + } + } + + fflush(MUNIT_OUTPUT_FILE); + + stderr_buf = NULL; +#if !defined(_WIN32) || defined(__MINGW32__) + stderr_buf = tmpfile(); +#else + tmpfile_s(&stderr_buf); +#endif + if (stderr_buf == NULL) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create buffer for stderr"); + result = MUNIT_ERROR; + goto print_result; + } + +#if !defined(MUNIT_NO_FORK) + if (runner->fork) { + pipefd[0] = -1; + pipefd[1] = -1; + if (pipe(pipefd) != 0) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create pipe"); + result = MUNIT_ERROR; + goto print_result; + } + + fork_pid = fork(); + if (fork_pid == 0) { + close(pipefd[0]); + + orig_stderr = munit_replace_stderr(stderr_buf); + munit_test_runner_exec(runner, test, params, &report); + + /* Note that we don't restore stderr. This is so we can buffer + * things written to stderr later on (such as by + * asan/tsan/ubsan, valgrind, etc.) */ + close(orig_stderr); + + do { + write_res = write(pipefd[1], ((munit_uint8_t*) (&report)) + bytes_written, sizeof(report) - bytes_written); + if (write_res < 0) { + if (stderr_buf != NULL) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to write to pipe"); + } + exit(EXIT_FAILURE); + } + bytes_written += write_res; + } while ((size_t) bytes_written < sizeof(report)); + + if (stderr_buf != NULL) + fclose(stderr_buf); + close(pipefd[1]); + + exit(EXIT_SUCCESS); + } else if (fork_pid == -1) { + close(pipefd[0]); + close(pipefd[1]); + if (stderr_buf != NULL) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to fork"); + } + report.errored++; + result = MUNIT_ERROR; + } else { + close(pipefd[1]); + do { + read_res = read(pipefd[0], ((munit_uint8_t*) (&report)) + bytes_read, sizeof(report) - bytes_read); + if (read_res < 1) + break; + bytes_read += read_res; + } while (bytes_read < (ssize_t) sizeof(report)); + + changed_pid = waitpid(fork_pid, &status, 0); + + if (MUNIT_LIKELY(changed_pid == fork_pid) && MUNIT_LIKELY(WIFEXITED(status))) { + if (bytes_read != sizeof(report)) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited unexpectedly with status %d", WEXITSTATUS(status)); + report.errored++; + } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited with status %d", WEXITSTATUS(status)); + report.errored++; + } + } else { + if (WIFSIGNALED(status)) { +#if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700) + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); +#else + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d", WTERMSIG(status)); +#endif + } else if (WIFSTOPPED(status)) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child stopped by signal %d", WSTOPSIG(status)); + } + report.errored++; + } + + close(pipefd[0]); + waitpid(fork_pid, NULL, 0); + } + } else +#endif + { +#if !defined(MUNIT_NO_BUFFER) + const volatile int orig_stderr = munit_replace_stderr(stderr_buf); +#endif + +#if defined(MUNIT_THREAD_LOCAL) + if (MUNIT_UNLIKELY(setjmp(munit_error_jmp_buf) != 0)) { + result = MUNIT_FAIL; + report.failed++; + } else { + munit_error_jmp_buf_valid = 1; + result = munit_test_runner_exec(runner, test, params, &report); + } +#else + result = munit_test_runner_exec(runner, test, params, &report); +#endif + +#if !defined(MUNIT_NO_BUFFER) + munit_restore_stderr(orig_stderr); +#endif + + /* Here just so that the label is used on Windows and we don't get + * a warning */ + goto print_result; + } + + print_result: + + fputs("[ ", MUNIT_OUTPUT_FILE); + if ((test->options & MUNIT_TEST_OPTION_TODO) == MUNIT_TEST_OPTION_TODO) { + if (report.failed != 0 || report.errored != 0 || report.skipped != 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_TODO, '3'); + result = MUNIT_OK; + } else { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); + if (MUNIT_LIKELY(stderr_buf != NULL)) + munit_log_internal(MUNIT_LOG_ERROR, stderr_buf, "Test marked TODO, but was successful."); + runner->report.failed++; + result = MUNIT_ERROR; + } + } else if (report.failed > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_FAIL, '1'); + runner->report.failed++; + result = MUNIT_FAIL; + } else if (report.errored > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); + runner->report.errored++; + result = MUNIT_ERROR; + } else if (report.skipped > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_SKIP, '3'); + runner->report.skipped++; + result = MUNIT_SKIP; + } else if (report.successful > 1) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); +#if defined(MUNIT_ENABLE_TIMING) + fputs(" ] [ ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock / report.successful); + fputs(" / ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock / report.successful); + fprintf(MUNIT_OUTPUT_FILE, " CPU ]\n %-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s Total: [ ", ""); + munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); + fputs(" / ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); + fputs(" CPU", MUNIT_OUTPUT_FILE); +#endif + runner->report.successful++; + result = MUNIT_OK; + } else if (report.successful > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); +#if defined(MUNIT_ENABLE_TIMING) + fputs(" ] [ ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); + fputs(" / ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); + fputs(" CPU", MUNIT_OUTPUT_FILE); +#endif + runner->report.successful++; + result = MUNIT_OK; + } + fputs(" ]\n", MUNIT_OUTPUT_FILE); + + if (stderr_buf != NULL) { + if (result == MUNIT_FAIL || result == MUNIT_ERROR || runner->show_stderr) { + fflush(MUNIT_OUTPUT_FILE); + + rewind(stderr_buf); + munit_splice(fileno(stderr_buf), STDERR_FILENO); + + fflush(stderr); + } + + fclose(stderr_buf); + } +} + +static void +munit_test_runner_run_test_wild(MunitTestRunner* runner, + const MunitTest* test, + const char* test_name, + MunitParameter* params, + MunitParameter* p) { + const MunitParameterEnum* pe; + char** values; + MunitParameter* next; + + for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) { + if (p->name == pe->name) + break; + } + + if (pe == NULL) + return; + + for (values = pe->values ; *values != NULL ; values++) { + next = p + 1; + p->value = *values; + if (next->name == NULL) { + munit_test_runner_run_test_with_params(runner, test, params); + } else { + munit_test_runner_run_test_wild(runner, test, test_name, params, next); + } + if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) + break; + } +} + +/* Run a single test, with every combination of parameters + * requested. */ +static void +munit_test_runner_run_test(MunitTestRunner* runner, + const MunitTest* test, + const char* prefix) { + char* test_name = munit_maybe_concat(NULL, (char*) prefix, (char*) test->name); + /* The array of parameters to pass to + * munit_test_runner_run_test_with_params */ + MunitParameter* params = NULL; + size_t params_l = 0; + /* Wildcard parameters are parameters which have possible values + * specified in the test, but no specific value was passed to the + * CLI. That means we want to run the test once for every + * possible combination of parameter values or, if --single was + * passed to the CLI, a single time with a random set of + * parameters. */ + MunitParameter* wild_params = NULL; + size_t wild_params_l = 0; + const MunitParameterEnum* pe; + const MunitParameter* cli_p; + munit_bool filled; + unsigned int possible; + char** vals; + size_t first_wild; + const MunitParameter* wp; + int pidx; + + munit_rand_seed(runner->seed); + + fprintf(MUNIT_OUTPUT_FILE, "%-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s", test_name); + + if (test->parameters == NULL) { + /* No parameters. Simple, nice. */ + munit_test_runner_run_test_with_params(runner, test, NULL); + } else { + fputc('\n', MUNIT_OUTPUT_FILE); + + for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) { + /* Did we received a value for this parameter from the CLI? */ + filled = 0; + for (cli_p = runner->parameters ; cli_p != NULL && cli_p->name != NULL ; cli_p++) { + if (strcmp(cli_p->name, pe->name) == 0) { + if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, cli_p->value) != MUNIT_OK)) + goto cleanup; + filled = 1; + break; + } + } + if (filled) + continue; + + /* Nothing from CLI, is the enum NULL/empty? We're not a + * fuzzer… */ + if (pe->values == NULL || pe->values[0] == NULL) + continue; + + /* If --single was passed to the CLI, choose a value from the + * list of possibilities randomly. */ + if (runner->single_parameter_mode) { + possible = 0; + for (vals = pe->values ; *vals != NULL ; vals++) + possible++; + /* We want the tests to be reproducible, even if you're only + * running a single test, but we don't want every test with + * the same number of parameters to choose the same parameter + * number, so use the test name as a primitive salt. */ + pidx = munit_rand_at_most(munit_str_hash(test_name), possible - 1); + if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[pidx]) != MUNIT_OK)) + goto cleanup; + } else { + /* We want to try every permutation. Put in a placeholder + * entry, we'll iterate through them later. */ + if (MUNIT_UNLIKELY(munit_parameters_add(&wild_params_l, &wild_params, pe->name, NULL) != MUNIT_OK)) + goto cleanup; + } + } + + if (wild_params_l != 0) { + first_wild = params_l; + for (wp = wild_params ; wp != NULL && wp->name != NULL ; wp++) { + for (pe = test->parameters ; pe != NULL && pe->name != NULL && pe->values != NULL ; pe++) { + if (strcmp(wp->name, pe->name) == 0) { + if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[0]) != MUNIT_OK)) + goto cleanup; + } + } + } + + munit_test_runner_run_test_wild(runner, test, test_name, params, params + first_wild); + } else { + munit_test_runner_run_test_with_params(runner, test, params); + } + + cleanup: + free(params); + free(wild_params); + } + + munit_maybe_free_concat(test_name, prefix, test->name); +} + +/* Recurse through the suite and run all the tests. If a list of + * tests to run was provied on the command line, run only those + * tests. */ +static void +munit_test_runner_run_suite(MunitTestRunner* runner, + const MunitSuite* suite, + const char* prefix) { + size_t pre_l; + char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix); + const MunitTest* test; + const char** test_name; + const MunitSuite* child_suite; + + /* Run the tests. */ + for (test = suite->tests ; test != NULL && test->test != NULL ; test++) { + if (runner->tests != NULL) { /* Specific tests were requested on the CLI */ + for (test_name = runner->tests ; test_name != NULL && *test_name != NULL ; test_name++) { + if ((pre_l == 0 || strncmp(pre, *test_name, pre_l) == 0) && + strncmp(test->name, *test_name + pre_l, strlen(*test_name + pre_l)) == 0) { + munit_test_runner_run_test(runner, test, pre); + if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) + goto cleanup; + } + } + } else { /* Run all tests */ + munit_test_runner_run_test(runner, test, pre); + } + } + + if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) + goto cleanup; + + /* Run any child suites. */ + for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) { + munit_test_runner_run_suite(runner, child_suite, pre); + } + + cleanup: + + munit_maybe_free_concat(pre, prefix, suite->prefix); +} + +static void +munit_test_runner_run(MunitTestRunner* runner) { + munit_test_runner_run_suite(runner, runner->suite, NULL); +} + +static void +munit_print_help(int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], void* user_data, const MunitArgument arguments[]) { + const MunitArgument* arg; + (void) argc; + + printf("USAGE: %s [OPTIONS...] [TEST...]\n\n", argv[0]); + puts(" --seed SEED\n" + " Value used to seed the PRNG. Must be a 32-bit integer in decimal\n" + " notation with no separators (commas, decimals, spaces, etc.), or\n" + " hexidecimal prefixed by \"0x\".\n" + " --iterations N\n" + " Run each test N times. 0 means the default number.\n" + " --param name value\n" + " A parameter key/value pair which will be passed to any test with\n" + " takes a parameter of that name. If not provided, the test will be\n" + " run once for each possible parameter value.\n" + " --list Write a list of all available tests.\n" + " --list-params\n" + " Write a list of all available tests and their possible parameters.\n" + " --single Run each parameterized test in a single configuration instead of\n" + " every possible combination\n" + " --log-visible debug|info|warning|error\n" + " --log-fatal debug|info|warning|error\n" + " Set the level at which messages of different severities are visible,\n" + " or cause the test to terminate.\n" +#if !defined(MUNIT_NO_FORK) + " --no-fork Do not execute tests in a child process. If this option is supplied\n" + " and a test crashes (including by failing an assertion), no further\n" + " tests will be performed.\n" +#endif + " --fatal-failures\n" + " Stop executing tests as soon as a failure is found.\n" + " --show-stderr\n" + " Show data written to stderr by the tests, even if the test succeeds.\n" + " --color auto|always|never\n" + " Colorize (or don't) the output.\n" + /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + " --help Print this help message and exit.\n"); +#if defined(MUNIT_NL_LANGINFO) + setlocale(LC_ALL, ""); + fputs((strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) ? "µnit" : "munit", stdout); +#else + puts("munit"); +#endif + printf(" %d.%d.%d\n" + "Full documentation at: https://nemequ.github.io/munit/\n", + (MUNIT_CURRENT_VERSION >> 16) & 0xff, + (MUNIT_CURRENT_VERSION >> 8) & 0xff, + (MUNIT_CURRENT_VERSION >> 0) & 0xff); + for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++) + arg->write_help(arg, user_data); +} + +static const MunitArgument* +munit_arguments_find(const MunitArgument arguments[], const char* name) { + const MunitArgument* arg; + + for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++) + if (strcmp(arg->name, name) == 0) + return arg; + + return NULL; +} + +static void +munit_suite_list_tests(const MunitSuite* suite, munit_bool show_params, const char* prefix) { + size_t pre_l; + char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix); + const MunitTest* test; + const MunitParameterEnum* params; + munit_bool first; + char** val; + const MunitSuite* child_suite; + + for (test = suite->tests ; + test != NULL && test->name != NULL ; + test++) { + if (pre != NULL) + fputs(pre, stdout); + puts(test->name); + + if (show_params) { + for (params = test->parameters ; + params != NULL && params->name != NULL ; + params++) { + fprintf(stdout, " - %s: ", params->name); + if (params->values == NULL) { + puts("Any"); + } else { + first = 1; + for (val = params->values ; + *val != NULL ; + val++ ) { + if(!first) { + fputs(", ", stdout); + } else { + first = 0; + } + fputs(*val, stdout); + } + putc('\n', stdout); + } + } + } + } + + for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) { + munit_suite_list_tests(child_suite, show_params, pre); + } + + munit_maybe_free_concat(pre, prefix, suite->prefix); +} + +static munit_bool +munit_stream_supports_ansi(FILE *stream) { +#if !defined(_WIN32) + return isatty(fileno(stream)); +#else + +#if !defined(__MINGW32__) + size_t ansicon_size = 0; +#endif + + if (isatty(fileno(stream))) { +#if !defined(__MINGW32__) + getenv_s(&ansicon_size, NULL, 0, "ANSICON"); + return ansicon_size != 0; +#else + return getenv("ANSICON") != NULL; +#endif + } + return 0; +#endif +} + +int +munit_suite_main_custom(const MunitSuite* suite, void* user_data, + int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], + const MunitArgument arguments[]) { + int result = EXIT_FAILURE; + MunitTestRunner runner; + size_t parameters_size = 0; + size_t tests_size = 0; + int arg; + + char* envptr; + unsigned long ts; + char* endptr; + unsigned long long iterations; + MunitLogLevel level; + const MunitArgument* argument; + const char** runner_tests; + unsigned int tests_run; + unsigned int tests_total; + + runner.prefix = NULL; + runner.suite = NULL; + runner.tests = NULL; + runner.seed = 0; + runner.iterations = 0; + runner.parameters = NULL; + runner.single_parameter_mode = 0; + runner.user_data = NULL; + + runner.report.successful = 0; + runner.report.skipped = 0; + runner.report.failed = 0; + runner.report.errored = 0; +#if defined(MUNIT_ENABLE_TIMING) + runner.report.cpu_clock = 0; + runner.report.wall_clock = 0; +#endif + + runner.colorize = 0; +#if !defined(_WIN32) + runner.fork = 1; +#else + runner.fork = 0; +#endif + runner.show_stderr = 0; + runner.fatal_failures = 0; + runner.suite = suite; + runner.user_data = user_data; + runner.seed = munit_rand_generate_seed(); + runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); + + for (arg = 1 ; arg < argc ; arg++) { + if (strncmp("--", argv[arg], 2) == 0) { + if (strcmp("seed", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); + goto cleanup; + } + + envptr = argv[arg + 1]; + ts = strtoul(argv[arg + 1], &envptr, 0); + if (*envptr != '\0' || ts > (~((munit_uint32_t) 0U))) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + runner.seed = (munit_uint32_t) ts; + + arg++; + } else if (strcmp("iterations", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); + goto cleanup; + } + + endptr = argv[arg + 1]; + iterations = strtoul(argv[arg + 1], &endptr, 0); + if (*endptr != '\0' || iterations > UINT_MAX) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + + runner.iterations = (unsigned int) iterations; + + arg++; + } else if (strcmp("param", argv[arg] + 2) == 0) { + if (arg + 2 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires two arguments", argv[arg]); + goto cleanup; + } + + runner.parameters = realloc(runner.parameters, sizeof(MunitParameter) * (parameters_size + 2)); + if (runner.parameters == NULL) { + munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory"); + goto cleanup; + } + runner.parameters[parameters_size].name = (char*) argv[arg + 1]; + runner.parameters[parameters_size].value = (char*) argv[arg + 2]; + parameters_size++; + runner.parameters[parameters_size].name = NULL; + runner.parameters[parameters_size].value = NULL; + arg += 2; + } else if (strcmp("color", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); + goto cleanup; + } + + if (strcmp(argv[arg + 1], "always") == 0) + runner.colorize = 1; + else if (strcmp(argv[arg + 1], "never") == 0) + runner.colorize = 0; + else if (strcmp(argv[arg + 1], "auto") == 0) + runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); + else { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + + arg++; + } else if (strcmp("help", argv[arg] + 2) == 0) { + munit_print_help(argc, argv, user_data, arguments); + result = EXIT_SUCCESS; + goto cleanup; + } else if (strcmp("single", argv[arg] + 2) == 0) { + runner.single_parameter_mode = 1; + } else if (strcmp("show-stderr", argv[arg] + 2) == 0) { + runner.show_stderr = 1; +#if !defined(_WIN32) + } else if (strcmp("no-fork", argv[arg] + 2) == 0) { + runner.fork = 0; +#endif + } else if (strcmp("fatal-failures", argv[arg] + 2) == 0) { + runner.fatal_failures = 1; + } else if (strcmp("log-visible", argv[arg] + 2) == 0 || + strcmp("log-fatal", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); + goto cleanup; + } + + if (strcmp(argv[arg + 1], "debug") == 0) + level = MUNIT_LOG_DEBUG; + else if (strcmp(argv[arg + 1], "info") == 0) + level = MUNIT_LOG_INFO; + else if (strcmp(argv[arg + 1], "warning") == 0) + level = MUNIT_LOG_WARNING; + else if (strcmp(argv[arg + 1], "error") == 0) + level = MUNIT_LOG_ERROR; + else { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + + if (strcmp("log-visible", argv[arg] + 2) == 0) + munit_log_level_visible = level; + else + munit_log_level_fatal = level; + + arg++; + } else if (strcmp("list", argv[arg] + 2) == 0) { + munit_suite_list_tests(suite, 0, NULL); + result = EXIT_SUCCESS; + goto cleanup; + } else if (strcmp("list-params", argv[arg] + 2) == 0) { + munit_suite_list_tests(suite, 1, NULL); + result = EXIT_SUCCESS; + goto cleanup; + } else { + argument = munit_arguments_find(arguments, argv[arg] + 2); + if (argument == NULL) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, "unknown argument ('%s')", argv[arg]); + goto cleanup; + } + + if (!argument->parse_argument(suite, user_data, &arg, argc, argv)) + goto cleanup; + } + } else { + runner_tests = realloc((void*) runner.tests, sizeof(char*) * (tests_size + 2)); + if (runner_tests == NULL) { + munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory"); + goto cleanup; + } + runner.tests = runner_tests; + runner.tests[tests_size++] = argv[arg]; + runner.tests[tests_size] = NULL; + } + } + + fflush(stderr); + fprintf(MUNIT_OUTPUT_FILE, "Running test suite with seed 0x%08" PRIx32 "...\n", runner.seed); + + munit_test_runner_run(&runner); + + tests_run = runner.report.successful + runner.report.failed + runner.report.errored; + tests_total = tests_run + runner.report.skipped; + if (tests_run == 0) { + fprintf(stderr, "No tests run, %d (100%%) skipped.\n", runner.report.skipped); + } else { + fprintf(MUNIT_OUTPUT_FILE, "%d of %d (%0.0f%%) tests successful, %d (%0.0f%%) test skipped.\n", + runner.report.successful, tests_run, + (((double) runner.report.successful) / ((double) tests_run)) * 100.0, + runner.report.skipped, + (((double) runner.report.skipped) / ((double) tests_total)) * 100.0); + } + + if (runner.report.failed == 0 && runner.report.errored == 0) { + result = EXIT_SUCCESS; + } + + cleanup: + free(runner.parameters); + free((void*) runner.tests); + + return result; +} + +int +munit_suite_main(const MunitSuite* suite, void* user_data, + int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]) { + return munit_suite_main_custom(suite, user_data, argc, argv, NULL); +} diff --git a/darshan-util/tests/unit-tests/munit/munit.h b/darshan-util/tests/unit-tests/munit/munit.h new file mode 100644 index 000000000..8460a4530 --- /dev/null +++ b/darshan-util/tests/unit-tests/munit/munit.h @@ -0,0 +1,535 @@ +/* µnit Testing Framework + * Copyright (c) 2013-2017 Evan Nemerson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if !defined(MUNIT_H) +#define MUNIT_H + +#include +#include + +#define MUNIT_VERSION(major, minor, revision) \ + (((major) << 16) | ((minor) << 8) | (revision)) + +#define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1) + +#if defined(_MSC_VER) && (_MSC_VER < 1600) +# define munit_int8_t __int8 +# define munit_uint8_t unsigned __int8 +# define munit_int16_t __int16 +# define munit_uint16_t unsigned __int16 +# define munit_int32_t __int32 +# define munit_uint32_t unsigned __int32 +# define munit_int64_t __int64 +# define munit_uint64_t unsigned __int64 +#else +# include +# define munit_int8_t int8_t +# define munit_uint8_t uint8_t +# define munit_int16_t int16_t +# define munit_uint16_t uint16_t +# define munit_int32_t int32_t +# define munit_uint32_t uint32_t +# define munit_int64_t int64_t +# define munit_uint64_t uint64_t +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1800) +# if !defined(PRIi8) +# define PRIi8 "i" +# endif +# if !defined(PRIi16) +# define PRIi16 "i" +# endif +# if !defined(PRIi32) +# define PRIi32 "i" +# endif +# if !defined(PRIi64) +# define PRIi64 "I64i" +# endif +# if !defined(PRId8) +# define PRId8 "d" +# endif +# if !defined(PRId16) +# define PRId16 "d" +# endif +# if !defined(PRId32) +# define PRId32 "d" +# endif +# if !defined(PRId64) +# define PRId64 "I64d" +# endif +# if !defined(PRIx8) +# define PRIx8 "x" +# endif +# if !defined(PRIx16) +# define PRIx16 "x" +# endif +# if !defined(PRIx32) +# define PRIx32 "x" +# endif +# if !defined(PRIx64) +# define PRIx64 "I64x" +# endif +# if !defined(PRIu8) +# define PRIu8 "u" +# endif +# if !defined(PRIu16) +# define PRIu16 "u" +# endif +# if !defined(PRIu32) +# define PRIu32 "u" +# endif +# if !defined(PRIu64) +# define PRIu64 "I64u" +# endif +#else +# include +#endif + +#if !defined(munit_bool) +# if defined(bool) +# define munit_bool bool +# elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define munit_bool _Bool +# else +# define munit_bool int +# endif +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__GNUC__) +# define MUNIT_LIKELY(expr) (__builtin_expect ((expr), 1)) +# define MUNIT_UNLIKELY(expr) (__builtin_expect ((expr), 0)) +# define MUNIT_UNUSED __attribute__((__unused__)) +#else +# define MUNIT_LIKELY(expr) (expr) +# define MUNIT_UNLIKELY(expr) (expr) +# define MUNIT_UNUSED +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__PGI) +# define MUNIT_ARRAY_PARAM(name) name +#else +# define MUNIT_ARRAY_PARAM(name) +#endif + +#if !defined(_WIN32) +# define MUNIT_SIZE_MODIFIER "z" +# define MUNIT_CHAR_MODIFIER "hh" +# define MUNIT_SHORT_MODIFIER "h" +#else +# if defined(_M_X64) || defined(__amd64__) +# define MUNIT_SIZE_MODIFIER "I64" +# else +# define MUNIT_SIZE_MODIFIER "" +# endif +# define MUNIT_CHAR_MODIFIER "" +# define MUNIT_SHORT_MODIFIER "" +#endif + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +# define MUNIT_NO_RETURN _Noreturn +#elif defined(__GNUC__) +# define MUNIT_NO_RETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) +# define MUNIT_NO_RETURN __declspec(noreturn) +#else +# define MUNIT_NO_RETURN +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1500) +# define MUNIT_PUSH_DISABLE_MSVC_C4127_ __pragma(warning(push)) __pragma(warning(disable:4127)) +# define MUNIT_POP_DISABLE_MSVC_C4127_ __pragma(warning(pop)) +#else +# define MUNIT_PUSH_DISABLE_MSVC_C4127_ +# define MUNIT_POP_DISABLE_MSVC_C4127_ +#endif + +typedef enum { + MUNIT_LOG_DEBUG, + MUNIT_LOG_INFO, + MUNIT_LOG_WARNING, + MUNIT_LOG_ERROR +} MunitLogLevel; + +#if defined(__GNUC__) && !defined(__MINGW32__) +# define MUNIT_PRINTF(string_index, first_to_check) __attribute__((format (printf, string_index, first_to_check))) +#else +# define MUNIT_PRINTF(string_index, first_to_check) +#endif + +MUNIT_PRINTF(4, 5) +void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...); + +#define munit_logf(level, format, ...) \ + munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__) + +#define munit_log(level, msg) \ + munit_logf(level, "%s", msg) + +MUNIT_NO_RETURN +MUNIT_PRINTF(3, 4) +void munit_errorf_ex(const char* filename, int line, const char* format, ...); + +#define munit_errorf(format, ...) \ + munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__) + +#define munit_error(msg) \ + munit_errorf("%s", msg) + +#define munit_assert(expr) \ + do { \ + if (!MUNIT_LIKELY(expr)) { \ + munit_error("assertion failed: " #expr); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_true(expr) \ + do { \ + if (!MUNIT_LIKELY(expr)) { \ + munit_error("assertion failed: " #expr " is not true"); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_false(expr) \ + do { \ + if (!MUNIT_LIKELY(!(expr))) { \ + munit_error("assertion failed: " #expr " is not false"); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \ + do { \ + T munit_tmp_a_ = (a); \ + T munit_tmp_b_ = (b); \ + if (!(munit_tmp_a_ op munit_tmp_b_)) { \ + munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix " %s " prefix "%" fmt suffix ")", \ + #a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_type(T, fmt, a, op, b) \ + munit_assert_type_full("", "", T, fmt, a, op, b) + +#define munit_assert_char(a, op, b) \ + munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b) +#define munit_assert_uchar(a, op, b) \ + munit_assert_type_full("'\\x", "'", unsigned char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b) +#define munit_assert_short(a, op, b) \ + munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b) +#define munit_assert_ushort(a, op, b) \ + munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b) +#define munit_assert_int(a, op, b) \ + munit_assert_type(int, "d", a, op, b) +#define munit_assert_uint(a, op, b) \ + munit_assert_type(unsigned int, "u", a, op, b) +#define munit_assert_long(a, op, b) \ + munit_assert_type(long int, "ld", a, op, b) +#define munit_assert_ulong(a, op, b) \ + munit_assert_type(unsigned long int, "lu", a, op, b) +#define munit_assert_llong(a, op, b) \ + munit_assert_type(long long int, "lld", a, op, b) +#define munit_assert_ullong(a, op, b) \ + munit_assert_type(unsigned long long int, "llu", a, op, b) + +#define munit_assert_size(a, op, b) \ + munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b) + +#define munit_assert_float(a, op, b) \ + munit_assert_type(float, "f", a, op, b) +#define munit_assert_double(a, op, b) \ + munit_assert_type(double, "g", a, op, b) +#define munit_assert_ptr(a, op, b) \ + munit_assert_type(const void*, "p", a, op, b) + +#define munit_assert_int8(a, op, b) \ + munit_assert_type(munit_int8_t, PRIi8, a, op, b) +#define munit_assert_uint8(a, op, b) \ + munit_assert_type(munit_uint8_t, PRIu8, a, op, b) +#define munit_assert_int16(a, op, b) \ + munit_assert_type(munit_int16_t, PRIi16, a, op, b) +#define munit_assert_uint16(a, op, b) \ + munit_assert_type(munit_uint16_t, PRIu16, a, op, b) +#define munit_assert_int32(a, op, b) \ + munit_assert_type(munit_int32_t, PRIi32, a, op, b) +#define munit_assert_uint32(a, op, b) \ + munit_assert_type(munit_uint32_t, PRIu32, a, op, b) +#define munit_assert_int64(a, op, b) \ + munit_assert_type(munit_int64_t, PRIi64, a, op, b) +#define munit_assert_uint64(a, op, b) \ + munit_assert_type(munit_uint64_t, PRIu64, a, op, b) + +#define munit_assert_double_equal(a, b, precision) \ + do { \ + const double munit_tmp_a_ = (a); \ + const double munit_tmp_b_ = (b); \ + const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \ + -(munit_tmp_a_ - munit_tmp_b_) : \ + (munit_tmp_a_ - munit_tmp_b_); \ + if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \ + munit_errorf("assertion failed: %s == %s (%0." #precision "g == %0." #precision "g)", \ + #a, #b, munit_tmp_a_, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#include +#define munit_assert_string_equal(a, b) \ + do { \ + const char* munit_tmp_a_ = a; \ + const char* munit_tmp_b_ = b; \ + if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \ + munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", \ + #a, #b, munit_tmp_a_, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_string_not_equal(a, b) \ + do { \ + const char* munit_tmp_a_ = a; \ + const char* munit_tmp_b_ = b; \ + if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \ + munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", \ + #a, #b, munit_tmp_a_, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_memory_equal(size, a, b) \ + do { \ + const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \ + const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \ + const size_t munit_tmp_size_ = (size); \ + if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != 0) { \ + size_t munit_tmp_pos_; \ + for (munit_tmp_pos_ = 0 ; munit_tmp_pos_ < munit_tmp_size_ ; munit_tmp_pos_++) { \ + if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \ + munit_errorf("assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER "u", \ + #a, #b, munit_tmp_pos_); \ + break; \ + } \ + } \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_memory_not_equal(size, a, b) \ + do { \ + const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \ + const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \ + const size_t munit_tmp_size_ = (size); \ + if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == 0) { \ + munit_errorf("assertion failed: memory %s != %s (%zu bytes)", \ + #a, #b, munit_tmp_size_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) \ + MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_ptr_equal(a, b) \ + munit_assert_ptr(a, ==, b) +#define munit_assert_ptr_not_equal(a, b) \ + munit_assert_ptr(a, !=, b) +#define munit_assert_null(ptr) \ + munit_assert_ptr(ptr, ==, NULL) +#define munit_assert_not_null(ptr) \ + munit_assert_ptr(ptr, !=, NULL) +#define munit_assert_ptr_null(ptr) \ + munit_assert_ptr(ptr, ==, NULL) +#define munit_assert_ptr_not_null(ptr) \ + munit_assert_ptr(ptr, !=, NULL) + +/*** Memory allocation ***/ + +void* munit_malloc_ex(const char* filename, int line, size_t size); + +#define munit_malloc(size) \ + munit_malloc_ex(__FILE__, __LINE__, (size)) + +#define munit_new(type) \ + ((type*) munit_malloc(sizeof(type))) + +#define munit_calloc(nmemb, size) \ + munit_malloc((nmemb) * (size)) + +#define munit_newa(type, nmemb) \ + ((type*) munit_calloc((nmemb), sizeof(type))) + +/*** Random number generation ***/ + +void munit_rand_seed(munit_uint32_t seed); +munit_uint32_t munit_rand_uint32(void); +int munit_rand_int_range(int min, int max); +double munit_rand_double(void); +void munit_rand_memory(size_t size, munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]); + +/*** Tests and Suites ***/ + +typedef enum { + /* Test successful */ + MUNIT_OK, + /* Test failed */ + MUNIT_FAIL, + /* Test was skipped */ + MUNIT_SKIP, + /* Test failed due to circumstances not intended to be tested + * (things like network errors, invalid parameter value, failure to + * allocate memory in the test harness, etc.). */ + MUNIT_ERROR +} MunitResult; + +typedef struct { + char* name; + char** values; +} MunitParameterEnum; + +typedef struct { + char* name; + char* value; +} MunitParameter; + +const char* munit_parameters_get(const MunitParameter params[], const char* key); + +typedef enum { + MUNIT_TEST_OPTION_NONE = 0, + MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0, + MUNIT_TEST_OPTION_TODO = 1 << 1 +} MunitTestOptions; + +typedef MunitResult (* MunitTestFunc)(const MunitParameter params[], void* user_data_or_fixture); +typedef void* (* MunitTestSetup)(const MunitParameter params[], void* user_data); +typedef void (* MunitTestTearDown)(void* fixture); + +typedef struct { + char* name; + MunitTestFunc test; + MunitTestSetup setup; + MunitTestTearDown tear_down; + MunitTestOptions options; + MunitParameterEnum* parameters; +} MunitTest; + +typedef enum { + MUNIT_SUITE_OPTION_NONE = 0 +} MunitSuiteOptions; + +typedef struct MunitSuite_ MunitSuite; + +struct MunitSuite_ { + char* prefix; + MunitTest* tests; + MunitSuite* suites; + unsigned int iterations; + MunitSuiteOptions options; +}; + +int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]); + +/* Note: I'm not very happy with this API; it's likely to change if I + * figure out something better. Suggestions welcome. */ + +typedef struct MunitArgument_ MunitArgument; + +struct MunitArgument_ { + char* name; + munit_bool (* parse_argument)(const MunitSuite* suite, void* user_data, int* arg, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]); + void (* write_help)(const MunitArgument* argument, void* user_data); +}; + +int munit_suite_main_custom(const MunitSuite* suite, + void* user_data, + int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], + const MunitArgument arguments[]); + +#if defined(MUNIT_ENABLE_ASSERT_ALIASES) + +#define assert_true(expr) munit_assert_true(expr) +#define assert_false(expr) munit_assert_false(expr) +#define assert_char(a, op, b) munit_assert_char(a, op, b) +#define assert_uchar(a, op, b) munit_assert_uchar(a, op, b) +#define assert_short(a, op, b) munit_assert_short(a, op, b) +#define assert_ushort(a, op, b) munit_assert_ushort(a, op, b) +#define assert_int(a, op, b) munit_assert_int(a, op, b) +#define assert_uint(a, op, b) munit_assert_uint(a, op, b) +#define assert_long(a, op, b) munit_assert_long(a, op, b) +#define assert_ulong(a, op, b) munit_assert_ulong(a, op, b) +#define assert_llong(a, op, b) munit_assert_llong(a, op, b) +#define assert_ullong(a, op, b) munit_assert_ullong(a, op, b) +#define assert_size(a, op, b) munit_assert_size(a, op, b) +#define assert_float(a, op, b) munit_assert_float(a, op, b) +#define assert_double(a, op, b) munit_assert_double(a, op, b) +#define assert_ptr(a, op, b) munit_assert_ptr(a, op, b) + +#define assert_int8(a, op, b) munit_assert_int8(a, op, b) +#define assert_uint8(a, op, b) munit_assert_uint8(a, op, b) +#define assert_int16(a, op, b) munit_assert_int16(a, op, b) +#define assert_uint16(a, op, b) munit_assert_uint16(a, op, b) +#define assert_int32(a, op, b) munit_assert_int32(a, op, b) +#define assert_uint32(a, op, b) munit_assert_uint32(a, op, b) +#define assert_int64(a, op, b) munit_assert_int64(a, op, b) +#define assert_uint64(a, op, b) munit_assert_uint64(a, op, b) + +#define assert_double_equal(a, b, precision) munit_assert_double_equal(a, b, precision) +#define assert_string_equal(a, b) munit_assert_string_equal(a, b) +#define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b) +#define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b) +#define assert_memory_not_equal(size, a, b) munit_assert_memory_not_equal(size, a, b) +#define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b) +#define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b) +#define assert_ptr_null(ptr) munit_assert_null_equal(ptr) +#define assert_ptr_not_null(ptr) munit_assert_not_null(ptr) + +#define assert_null(ptr) munit_assert_null(ptr) +#define assert_not_null(ptr) munit_assert_not_null(ptr) + +#endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */ + +#if defined(__cplusplus) +} +#endif + +#endif /* !defined(MUNIT_H) */ + +#if defined(MUNIT_ENABLE_ASSERT_ALIASES) +# if defined(assert) +# undef assert +# endif +# define assert(expr) munit_assert(expr) +#endif