diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93292e6..73d4df1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,15 @@ jobs: msystem: MINGW64 install: mingw-w64-x86_64-libpng + # setup-msys2 only adds the MSYS2 root to the Windows PATH, so the test + # executable would not find libpng16-16.dll (and libz-1.dll) at runtime and + # fails with 0xc0000135 (STATUS_DLL_NOT_FOUND). Add the mingw64 bin + # directory to the PATH for the build/test steps. + - name: Add MSYS2 mingw64 bin to PATH (Windows) + if: runner.os == 'Windows' + run: | + Add-Content "$env:GITHUB_PATH" "${{ steps.msys2.outputs.msys2-location }}/mingw64/bin" + - name: Configure CMake (Windows) if: runner.os == 'Windows' run: > diff --git a/tests/test_OpenSimplex2F.c b/tests/test_OpenSimplex2F.c index 2b62890..f1bad93 100644 --- a/tests/test_OpenSimplex2F.c +++ b/tests/test_OpenSimplex2F.c @@ -4,6 +4,7 @@ #endif #include #include +#include #include @@ -68,17 +69,94 @@ static int write_png_image(const char *filename, unsigned char *pixels, int w, i return rc; } +/* --------------------------------------------------------------------------- + * Readable test output: every generated image is checked (noise value range + * and PNG file) and reported line by line, so the CI log shows what the + * test did and why it passed or failed. + * ------------------------------------------------------------------------- */ + +struct noise_stats { + double min; + double max; + double sum; +}; + +static void stats_init(struct noise_stats *s) +{ + s->min = 1e300; + s->max = -1e300; + s->sum = 0.0; +} + +static void stats_add(struct noise_stats *s, double value) +{ + if (value < s->min) + s->min = value; + if (value > s->max) + s->max = value; + s->sum += value; +} + +/* All OpenSimplex2F variants are defined to return values in [-1, 1]. + * The 3-octave blend of the 2D image is a convex combination of the same + * functions, so it stays in the same range. A small tolerance covers + * floating point rounding. */ +#define NOISE_LIMIT 1.0 +#define NOISE_EPSILON 1e-6 + +static int report_test(int index, int total, const char *name, const char *png, + int write_rc, const struct noise_stats *s) +{ + struct stat file; + long long size = 0; + double mean = s->sum / (double) (WIDTH * HEIGHT); + int in_range = (s->min >= -NOISE_LIMIT - NOISE_EPSILON) && + (s->max <= NOISE_LIMIT + NOISE_EPSILON); + int wrote = (write_rc == 0) && (stat(png, &file) == 0); + int ok = in_range && wrote; + + if (wrote) + size = (long long) file.st_size; + printf(" [%d/%d] %-32s min=% .6f max=% .6f mean=% .6f -> %s (%lld bytes) ... %s\n", + index, total, name, s->min, s->max, mean, png, size, + ok ? "PASS" : "FAIL"); + if (!in_range) + printf(" noise value outside the expected range [-1, 1]\n"); + if (write_rc != 0) + printf(" write_png_image(%s) failed\n", png); + else if (!wrote) + printf(" %s is missing or unreadable\n", png); + return ok; +} + int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[]) { int x, y; double value; double v0, v1, v2; /* values from different octaves. */ uint32_t rgb; - uint32_t image2d[HEIGHT][WIDTH]; - uint32_t image3d[HEIGHT][WIDTH]; - uint32_t image4d[HEIGHT][WIDTH]; + int passed = 0; + /* Static storage: 3 x 1MB arrays would not fit the default Windows + * thread stack (1MB) and crash with STATUS_STACK_BUFFER_OVERRUN + * (0xC000013D); Linux survives them only because its default stack + * is 8MB. + */ + static uint32_t image2d[HEIGHT][WIDTH]; + static uint32_t image3d[HEIGHT][WIDTH]; + static uint32_t image4d[HEIGHT][WIDTH]; struct OpenSimplex2F_context *ctx; + struct noise_stats st2d, st3d, st4d; +#if defined(SINGLE_OCTAVE) + static const char *name2d = "open_simplex_noise4 (1 octave)"; +#else + static const char *name2d = "open_simplex_noise4 (3 octaves)"; +#endif + + stats_init(&st2d); + stats_init(&st3d); + stats_init(&st4d); + printf("OpenSimplex2F tests (seed 77374, image %dx%d)\n", WIDTH, HEIGHT); OpenSimplex2F(77374, &ctx); for (y = 0; y < HEIGHT; y++) { @@ -95,23 +173,36 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[]) (double) y / FEATURE_SIZE / 1, 0.0, 0.0); value = v0 * 4 / 7.0 + v1 * 2 / 7.0 + v2 * 1 / 7.0; #endif + stats_add(&st2d, value); rgb = 0x010101 * (uint32_t) ((value + 1) * 127.5); image2d[y][x] = ((uint32_t) 0x0ff << 24) | (rgb); value = OpenSimplex2F_noise2(ctx, (double) x / FEATURE_SIZE, (double) y / FEATURE_SIZE); + stats_add(&st3d, value); rgb = 0x010101 * (uint32_t) ((value + 1) * 127.5); image3d[y][x] = ((uint32_t) 0x0ff << 24) | (rgb); value = OpenSimplex2F_noise3_Classic(ctx, (double) x / FEATURE_SIZE, (double) y / FEATURE_SIZE, 0.0); + stats_add(&st4d, value); rgb = 0x010101 * (uint32_t) ((value + 1) * 127.5); image4d[y][x] = ((uint32_t) 0x0ff << 24) | (rgb); } } - write_png_image("test2d.png", (unsigned char *) image2d, WIDTH, HEIGHT, 1); - write_png_image("test3d.png", (unsigned char *) image3d, WIDTH, HEIGHT, 1); - write_png_image("test4d.png", (unsigned char *) image4d, WIDTH, HEIGHT, 1); + if (report_test(1, 3, name2d, "test2d.png", + write_png_image("test2d.png", (unsigned char *) image2d, WIDTH, HEIGHT, 1), + &st2d)) + passed++; + if (report_test(2, 3, "OpenSimplex2F_noise2", "test3d.png", + write_png_image("test3d.png", (unsigned char *) image3d, WIDTH, HEIGHT, 1), + &st3d)) + passed++; + if (report_test(3, 3, "OpenSimplex2F_noise3_Classic", "test4d.png", + write_png_image("test4d.png", (unsigned char *) image4d, WIDTH, HEIGHT, 1), + &st4d)) + passed++; OpenSimplex2F_free(ctx); OpenSimplex2F_shutdown(); - return 0; + printf("Result: %d/3 tests passed\n", passed); + return (passed == 3) ? 0 : 1; }