Hist

Hist test source.

/******************************************************************************
* Copyright (c) Intel Corporation - All rights reserved.                      *
* This file is part of the LIBXS library.                                     *
*                                                                             *
* For information on the license, see the LICENSE file.                       *
* Further information: https://github.com/hfp/libxs/                          *
* SPDX-License-Identifier: BSD-3-Clause                                       *
******************************************************************************/
#include <libxs_hist.h>
#include <math.h>

#if defined(_DEBUG)
# define FPRINTF(STREAM, ...) do { fprintf(STREAM, __VA_ARGS__); } while(0)
#else
# define FPRINTF(STREAM, ...) do {} while(0)
#endif

#if !defined(TOLERANCE)
# define TOLERANCE 1E-6
#endif


static int test_create_destroy(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(4/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) {
    FPRINTF(stderr, "ERROR line #%i: hist_create failed\n", __LINE__);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  libxs_hist_destroy(NULL);
  return result;
}


static int test_single_value(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  const double value[] = { 42.0 };
  hist = libxs_hist_create(1/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  libxs_hist_push(NULL, hist, value);
  libxs_hist_query(NULL, hist, &info);
  if (1 != info.nbuckets || 1 != info.nvals || NULL == info.buckets || NULL == info.vals) {
    FPRINTF(stderr, "ERROR line #%i: unexpected get result\n", __LINE__);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && fabs(info.vals[0] - 42.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected value 42.0, got %f\n", __LINE__, info.vals[0]);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && (fabs(info.range[0] - 42.0) > TOLERANCE || fabs(info.range[1] - 42.0) > TOLERANCE)) {
    FPRINTF(stderr, "ERROR line #%i: range mismatch [%f, %f]\n", __LINE__, info.range[0], info.range[1]);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && 1 != info.nsamples) {
    FPRINTF(stderr, "ERROR line #%i: expected nsamples=1, got %i\n", __LINE__, info.nsamples);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_fill_phase_range(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  const double v1[] = { 10.0 }, v2[] = { 20.0 }, v3[] = { 15.0 };
  hist = libxs_hist_create(4/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  libxs_hist_push(NULL, hist, v1);
  libxs_hist_push(NULL, hist, v2);
  libxs_hist_push(NULL, hist, v3);
  libxs_hist_query(NULL, hist, &info);
  if (fabs(info.range[0] - 10.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected min=10.0, got %f\n", __LINE__, info.range[0]);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && fabs(info.range[1] - 20.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected max=20.0, got %f\n", __LINE__, info.range[1]);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_bucket_distribution(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_add };
  libxs_hist_info_t info;
  int i, total;
  int result = EXIT_SUCCESS;
  const double vmin[] = { 0.0 }, vmax[] = { 100.0 };
  hist = libxs_hist_create(4/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  libxs_hist_push(NULL, hist, vmin);
  libxs_hist_push(NULL, hist, vmax);
  {
    const double v33[] = { 33.0 }, v66[] = { 66.0 };
    libxs_hist_push(NULL, hist, v33);
    libxs_hist_push(NULL, hist, v66);
  }
  for (i = 0; i < 40; ++i) {
    const double v[] = { 2.5 * i };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query(NULL, hist, &info);
  if (4 != info.nbuckets || NULL == info.buckets) {
    FPRINTF(stderr, "ERROR line #%i: unexpected nbuckets=%i\n", __LINE__, info.nbuckets);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result) {
    for (total = 0, i = 0; i < info.nbuckets; ++i) {
      if (info.buckets[i] < 0) {
        FPRINTF(stderr, "ERROR line #%i: negative bucket[%i]=%i\n", __LINE__, i, info.buckets[i]);
        result = EXIT_FAILURE;
        break;
      }
      total += info.buckets[i];
    }
    if (EXIT_SUCCESS == result && total < 40) {
      FPRINTF(stderr, "ERROR line #%i: total count %i < 40\n", __LINE__, total);
      result = EXIT_FAILURE;
    }
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_update_add(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_add };
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(1/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double v[] = { 5.0 };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query(NULL, hist, &info);
  if (1 != info.nbuckets || NULL == info.vals) {
    FPRINTF(stderr, "ERROR line #%i: unexpected state\n", __LINE__);
    libxs_hist_destroy(hist);
    return EXIT_FAILURE;
  }
  {
    const double v1[] = { 5.0 }, v2[] = { 5.0 }, v3[] = { 5.0 };
    libxs_hist_push(NULL, hist, v1);
    libxs_hist_push(NULL, hist, v2);
    libxs_hist_push(NULL, hist, v3);
  }
  libxs_hist_query(NULL, hist, &info);
  if (NULL == info.vals || NULL == info.buckets) {
    FPRINTF(stderr, "ERROR line #%i: get failed\n", __LINE__);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && info.buckets[0] < 2) {
    FPRINTF(stderr, "ERROR line #%i: expected bucket count >= 2, got %i\n", __LINE__, info.buckets[0]);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_update_avg(void)
{
  double a = 10.0;
  const double b = 20.0;
  int result = EXIT_SUCCESS;
  /* Welford: mean of {10, 20} = 15.0 */
  libxs_hist_update_avg(&a, &b, 2);
  if (fabs(a - 15.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 15.0, got %f\n", __LINE__, a);
    result = EXIT_FAILURE;
  }
  /* Welford: mean of {10, 20, 20} = 16.667 */
  libxs_hist_update_avg(&a, &b, 3);
  if (fabs(a - 50.0 / 3) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 16.667, got %f\n", __LINE__, a);
    result = EXIT_FAILURE;
  }
  return result;
}


static int test_update_add_fn(void)
{
  double a = 3.0;
  const double b = 7.0;
  int result = EXIT_SUCCESS;
  libxs_hist_update_add(&a, &b, 2);
  if (fabs(a - 10.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 10.0, got %f\n", __LINE__, a);
    result = EXIT_FAILURE;
  }
  libxs_hist_update_add(&a, &b, 3);
  if (fabs(a - 17.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 17.0, got %f\n", __LINE__, a);
    result = EXIT_FAILURE;
  }
  return result;
}


static int test_update_min_max(void)
{
  double mn = 5.0, mx = 5.0;
  const double lo = 2.0, hi = 8.0;
  int result = EXIT_SUCCESS;
  libxs_hist_update_min(&mn, &lo, 2);
  if (fabs(mn - 2.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 2.0, got %f\n", __LINE__, mn);
    result = EXIT_FAILURE;
  }
  libxs_hist_update_min(&mn, &hi, 3);
  if (EXIT_SUCCESS == result && fabs(mn - 2.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 2.0, got %f\n", __LINE__, mn);
    result = EXIT_FAILURE;
  }
  libxs_hist_update_max(&mx, &hi, 2);
  if (EXIT_SUCCESS == result && fabs(mx - 8.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 8.0, got %f\n", __LINE__, mx);
    result = EXIT_FAILURE;
  }
  libxs_hist_update_max(&mx, &lo, 3);
  if (EXIT_SUCCESS == result && fabs(mx - 8.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 8.0, got %f\n", __LINE__, mx);
    result = EXIT_FAILURE;
  }
  return result;
}


static int test_multiple_values(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg, libxs_hist_update_add };
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(2/*nbuckets*/, 2/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double e1[] = { 0.0, 100.0 }, e2[] = { 10.0, 200.0 };
    libxs_hist_push(NULL, hist, e1);
    libxs_hist_push(NULL, hist, e2);
  }
  libxs_hist_query(NULL, hist, &info);
  if (2 != info.nbuckets || 2 != info.nvals || NULL == info.buckets || NULL == info.vals) {
    FPRINTF(stderr, "ERROR line #%i: unexpected state nbuckets=%i nvals=%i\n",
      __LINE__, info.nbuckets, info.nvals);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result) {
    if (fabs(info.range[0] - 0.0) > TOLERANCE || fabs(info.range[1] - 10.0) > TOLERANCE) {
      FPRINTF(stderr, "ERROR line #%i: range [%f, %f] unexpected\n", __LINE__, info.range[0], info.range[1]);
      result = EXIT_FAILURE;
    }
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_print(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(3/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double v1[] = { 1.0 }, v2[] = { 2.0 }, v3[] = { 3.0 };
    libxs_hist_push(NULL, hist, v1);
    libxs_hist_push(NULL, hist, v2);
    libxs_hist_push(NULL, hist, v3);
  }
  {
#if defined(_DEBUG)
    FILE *const ostream = stderr;
#elif !defined(_WIN32)
    FILE *const ostream = fopen("/dev/null", "w");
#else
    FILE *const ostream = fopen("NUL", "w");
#endif
    if (NULL != ostream) {
      const int prec[] = { 2 };
      libxs_hist_print(ostream, hist, prec, "test_print");
#if !defined(_DEBUG)
      fclose(ostream);
#endif
    }
  }
  libxs_hist_print(NULL, hist, NULL, "null_stream");
  libxs_hist_destroy(hist);
  return result;
}


static int test_set_null_hist(void)
{
  int result = EXIT_SUCCESS;
  const double value[] = { 1.0 };
  libxs_hist_push(NULL, NULL, value);
  return result;
}


static int test_get_null_hist(void)
{
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  libxs_hist_query(NULL, NULL, &info);
  if (0 != info.nbuckets || 0 != info.nvals || NULL != info.buckets || NULL != info.vals) {
    FPRINTF(stderr, "ERROR line #%i: get on NULL hist should yield empty\n", __LINE__);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && (fabs(info.range[0]) > TOLERANCE || fabs(info.range[1]) > TOLERANCE)) {
    FPRINTF(stderr, "ERROR line #%i: range should be [0,0] for NULL hist\n", __LINE__);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && 0 != info.nsamples) {
    FPRINTF(stderr, "ERROR line #%i: nsamples should be 0 for NULL hist\n", __LINE__);
    result = EXIT_FAILURE;
  }
  return result;
}


static int test_many_values_bucketing(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int i, total;
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(10/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    for (i = 0; i < 10; ++i) {
      const double fv[] = { 200.0 * i / 9 };
      libxs_hist_push(NULL, hist, fv);
    }
  }
  for (i = 0; i < 100; ++i) {
    const double v[] = { 2.0 * i };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query(NULL, hist, &info);
  if (10 != info.nbuckets || NULL == info.buckets) {
    FPRINTF(stderr, "ERROR line #%i: nbuckets=%i\n", __LINE__, info.nbuckets);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result) {
    for (total = 0, i = 0; i < info.nbuckets; ++i) total += info.buckets[i];
    if (total < 10) {
      FPRINTF(stderr, "ERROR line #%i: total=%i < 10\n", __LINE__, total);
      result = EXIT_FAILURE;
    }
    for (i = 0; i < info.nbuckets && EXIT_SUCCESS == result; ++i) {
      if (0 == info.buckets[i]) {
        FPRINTF(stderr, "ERROR line #%i: bucket[%i] is empty\n", __LINE__, i);
        result = EXIT_FAILURE;
      }
    }
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_underpopulated(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int i, total;
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(8/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double v1[] = { 10.0 }, v2[] = { 50.0 }, v3[] = { 90.0 };
    libxs_hist_push(NULL, hist, v1);
    libxs_hist_push(NULL, hist, v2);
    libxs_hist_push(NULL, hist, v3);
  }
  libxs_hist_query(NULL, hist, &info);
  if (3 != info.nbuckets) {
    FPRINTF(stderr, "ERROR line #%i: expected 3 buckets, got %i\n", __LINE__, info.nbuckets);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && (NULL == info.buckets || NULL == info.vals || 1 != info.nvals)) {
    FPRINTF(stderr, "ERROR line #%i: unexpected NULL or nvals=%i\n", __LINE__, info.nvals);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result) {
    for (total = 0, i = 0; i < info.nbuckets; ++i) total += info.buckets[i];
    if (3 != total) {
      FPRINTF(stderr, "ERROR line #%i: total=%i (expected 3)\n", __LINE__, total);
      result = EXIT_FAILURE;
    }
  }
  if (EXIT_SUCCESS == result && (fabs(info.range[0] - 10.0) > TOLERANCE || fabs(info.range[1] - 90.0) > TOLERANCE)) {
    FPRINTF(stderr, "ERROR line #%i: range [%f, %f] unexpected\n", __LINE__, info.range[0], info.range[1]);
    result = EXIT_FAILURE;
  }
  {
    const double v4[] = { 30.0 }, v5[] = { 70.0 };
    libxs_hist_push(NULL, hist, v4);
    libxs_hist_push(NULL, hist, v5);
  }
  libxs_hist_query(NULL, hist, &info);
  if (EXIT_SUCCESS == result) {
    for (total = 0, i = 0; i < info.nbuckets; ++i) total += info.buckets[i];
    if (5 != total) {
      FPRINTF(stderr, "ERROR line #%i: total=%i after more inserts (expected 5)\n", __LINE__, total);
      result = EXIT_FAILURE;
    }
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_commit_arithmetic_avg(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  /* 1 bucket, nqueue=4: all 4 values land in the same bucket at commit.
   * Arithmetic mean of {10, 20, 30, 40} = 25.0
   */
  hist = libxs_hist_create(1/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double v1[] = { 10.0 }, v2[] = { 20.0 }, v3[] = { 30.0 }, v4[] = { 40.0 };
    libxs_hist_push(NULL, hist, v1);
    libxs_hist_push(NULL, hist, v2);
    libxs_hist_push(NULL, hist, v3);
    libxs_hist_push(NULL, hist, v4);
  }
  libxs_hist_query(NULL, hist, &info);
  if (1 != info.nbuckets || NULL == info.vals || NULL == info.buckets) {
    FPRINTF(stderr, "ERROR line #%i: unexpected state\n", __LINE__);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && fabs(info.vals[0] - 25.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 25.0 (arithmetic mean), got %f\n", __LINE__, info.vals[0]);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && 4 != info.buckets[0]) {
    FPRINTF(stderr, "ERROR line #%i: expected count=4, got %i\n", __LINE__, info.buckets[0]);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_hybrid_avg_then_welford(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int result = EXIT_SUCCESS;
  /* 1 bucket, nqueue=2: commit produces arithmetic mean,
   * then subsequent inserts use Welford.
   * Queue: {10, 30} -> commit: mean=20.0
   * Welford with 40.0 (count=3): 20 + (40-20)/3 = 26.667
   */
  hist = libxs_hist_create(1/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double v1[] = { 10.0 }, v2[] = { 30.0 };
    libxs_hist_push(NULL, hist, v1);
    libxs_hist_push(NULL, hist, v2);
  }
  libxs_hist_query(NULL, hist, &info);
  if (EXIT_SUCCESS == result && fabs(info.vals[0] - 20.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 20.0 after commit, got %f\n", __LINE__, info.vals[0]);
    result = EXIT_FAILURE;
  }
  {
    const double v3[] = { 40.0 };
    libxs_hist_push(NULL, hist, v3);
  }
  libxs_hist_query(NULL, hist, &info);
  if (EXIT_SUCCESS == result && fabs(info.vals[0] - 80.0 / 3) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 26.667 after Welford, got %f\n", __LINE__, info.vals[0]);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_nsamples(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  libxs_hist_info_t info;
  int i;
  int result = EXIT_SUCCESS;
  hist = libxs_hist_create(4/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  for (i = 0; i < 20; ++i) {
    const double v[] = { (double)i };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query(NULL, hist, &info);
  if (20 != info.nsamples) {
    FPRINTF(stderr, "ERROR line #%i: expected nsamples=20, got %i\n", __LINE__, info.nsamples);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_median_uniform(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  int result = EXIT_SUCCESS;
  double vals[1];
  int i;
  hist = libxs_hist_create(10/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  for (i = 0; i <= 100; ++i) {
    const double v[] = { (double)i };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query_median(NULL, hist, vals);
  if (fabs(vals[0] - 50.0) > 5.0 + TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected median ~50.0, got %f\n", __LINE__, vals[0]);
    result = EXIT_FAILURE;
  }
  {
    double p0[1], p1[1];
    libxs_hist_query_percentile(NULL, hist, p0, 0.0);
    libxs_hist_query_percentile(NULL, hist, p1, 1.0);
    if (p0[0] > 10.0 + TOLERANCE) {
      FPRINTF(stderr, "ERROR line #%i: percentile(0)=%f too high\n", __LINE__, p0[0]);
      result = EXIT_FAILURE;
    }
    if (p1[0] < 90.0 - TOLERANCE) {
      FPRINTF(stderr, "ERROR line #%i: percentile(1)=%f too low\n", __LINE__, p1[0]);
      result = EXIT_FAILURE;
    }
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_median_single(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg };
  int result = EXIT_SUCCESS;
  double vals[1];
  hist = libxs_hist_create(4/*nbuckets*/, 1/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  {
    const double v[] = { 42.0 };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query_median(NULL, hist, vals);
  if (fabs(vals[0] - 42.0) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected 42.0, got %f\n", __LINE__, vals[0]);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


static int test_median_null(void)
{
  int result = EXIT_SUCCESS;
  double vals[1] = { -1.0 };
  libxs_hist_query_median(NULL, NULL, vals);
  if (fabs(vals[0] - (-1.0)) > TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected -1.0 (untouched), got %f\n", __LINE__, vals[0]);
    result = EXIT_FAILURE;
  }
  return result;
}


static int test_percentile_vals(void)
{
  libxs_hist_t* hist = NULL;
  const libxs_hist_update_t update[] = { libxs_hist_update_avg, libxs_hist_update_avg };
  int result = EXIT_SUCCESS;
  double vals[2];
  int i;
  hist = libxs_hist_create(4/*nbuckets*/, 2/*nvals*/, update);
  if (NULL == hist) return EXIT_FAILURE;
  for (i = 0; i <= 40; ++i) {
    const double v[] = { (double)i, 100.0 + i };
    libxs_hist_push(NULL, hist, v);
  }
  libxs_hist_query_median(NULL, hist, vals);
  if (fabs(vals[0] - 20.0) > 5.0 + TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected median ~20.0, got %f\n", __LINE__, vals[0]);
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS == result && fabs(vals[1] - 120.0) > 10.0 + TOLERANCE) {
    FPRINTF(stderr, "ERROR line #%i: expected aux ~120.0, got %f\n", __LINE__, vals[1]);
    result = EXIT_FAILURE;
  }
  libxs_hist_destroy(hist);
  return result;
}


int main(void)
{
  int result = EXIT_SUCCESS;

  if (EXIT_SUCCESS != test_create_destroy()) {
    FPRINTF(stderr, "FAILED: test_create_destroy\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_single_value()) {
    FPRINTF(stderr, "FAILED: test_single_value\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_fill_phase_range()) {
    FPRINTF(stderr, "FAILED: test_fill_phase_range\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_bucket_distribution()) {
    FPRINTF(stderr, "FAILED: test_bucket_distribution\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_update_add()) {
    FPRINTF(stderr, "FAILED: test_update_add\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_update_avg()) {
    FPRINTF(stderr, "FAILED: test_update_avg\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_update_add_fn()) {
    FPRINTF(stderr, "FAILED: test_update_add_fn\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_update_min_max()) {
    FPRINTF(stderr, "FAILED: test_update_min_max\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_multiple_values()) {
    FPRINTF(stderr, "FAILED: test_multiple_values\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_print()) {
    FPRINTF(stderr, "FAILED: test_print\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_set_null_hist()) {
    FPRINTF(stderr, "FAILED: test_set_null_hist\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_get_null_hist()) {
    FPRINTF(stderr, "FAILED: test_get_null_hist\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_many_values_bucketing()) {
    FPRINTF(stderr, "FAILED: test_many_values_bucketing\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_underpopulated()) {
    FPRINTF(stderr, "FAILED: test_underpopulated\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_commit_arithmetic_avg()) {
    FPRINTF(stderr, "FAILED: test_commit_arithmetic_avg\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_hybrid_avg_then_welford()) {
    FPRINTF(stderr, "FAILED: test_hybrid_avg_then_welford\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_nsamples()) {
    FPRINTF(stderr, "FAILED: test_nsamples\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_median_uniform()) {
    FPRINTF(stderr, "FAILED: test_median_uniform\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_median_single()) {
    FPRINTF(stderr, "FAILED: test_median_single\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_median_null()) {
    FPRINTF(stderr, "FAILED: test_median_null\n");
    result = EXIT_FAILURE;
  }
  if (EXIT_SUCCESS != test_percentile_vals()) {
    FPRINTF(stderr, "FAILED: test_percentile_vals\n");
    result = EXIT_FAILURE;
  }

  return result;
}