Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/regutils.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /*====================================================================* | |
| 2 - Copyright (C) 2001 Leptonica. All rights reserved. | |
| 3 - | |
| 4 - Redistribution and use in source and binary forms, with or without | |
| 5 - modification, are permitted provided that the following conditions | |
| 6 - are met: | |
| 7 - 1. Redistributions of source code must retain the above copyright | |
| 8 - notice, this list of conditions and the following disclaimer. | |
| 9 - 2. Redistributions in binary form must reproduce the above | |
| 10 - copyright notice, this list of conditions and the following | |
| 11 - disclaimer in the documentation and/or other materials | |
| 12 - provided with the distribution. | |
| 13 - | |
| 14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY | |
| 18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 *====================================================================*/ | |
| 26 | |
| 27 | |
| 28 /*! | |
| 29 * \file regutils.c | |
| 30 * <pre> | |
| 31 * | |
| 32 * Regression test utilities | |
| 33 * l_int32 regTestSetup() | |
| 34 * l_int32 regTestCleanup() | |
| 35 * l_int32 regTestCompareValues() | |
| 36 * l_int32 regTestCompareStrings() | |
| 37 * l_int32 regTestComparePix() | |
| 38 * l_int32 regTestCompareSimilarPix() | |
| 39 * l_int32 regTestCheckFile() | |
| 40 * l_int32 regTestCompareFiles() | |
| 41 * l_int32 regTestWritePixAndCheck() | |
| 42 * l_int32 regTestWriteDataAndCheck() | |
| 43 * char *regTestGenLocalFilename() | |
| 44 * | |
| 45 * Static function | |
| 46 * char *getRootNameFromArgv0() | |
| 47 * | |
| 48 * These functions are for testing and development. They are not intended | |
| 49 * for use with programs that run in a production environment, such as a | |
| 50 * cloud service with unrestricted access. | |
| 51 * | |
| 52 * See regutils.h for how to use this. Here is a minimal setup: | |
| 53 * | |
| 54 * main(int argc, char **argv) { | |
| 55 * ... | |
| 56 * L_REGPARAMS *rp; | |
| 57 * | |
| 58 * if (regTestSetup(argc, argv, &rp)) | |
| 59 * return 1; | |
| 60 * ... | |
| 61 * regTestWritePixAndCheck(rp, pix, IFF_PNG); // 0 | |
| 62 * ... | |
| 63 * return regTestCleanup(rp); | |
| 64 * } | |
| 65 * </pre> | |
| 66 */ | |
| 67 | |
| 68 #ifdef HAVE_CONFIG_H | |
| 69 #include <config_auto.h> | |
| 70 #endif /* HAVE_CONFIG_H */ | |
| 71 | |
| 72 #include <string.h> | |
| 73 #include "allheaders.h" | |
| 74 | |
| 75 extern l_int32 NumImageFileFormatExtensions; | |
| 76 extern const char *ImageFileFormatExtensions[]; | |
| 77 | |
| 78 static char *getRootNameFromArgv0(const char *argv0); | |
| 79 | |
| 80 | |
| 81 /*--------------------------------------------------------------------* | |
| 82 * Regression test utilities * | |
| 83 *--------------------------------------------------------------------*/ | |
| 84 /*! | |
| 85 * \brief regTestSetup() | |
| 86 * | |
| 87 * \param[in] argc from invocation; can be either 1 or 2 | |
| 88 * \param[in] argv to regtest: %argv[1] is one of these: | |
| 89 * "generate", "compare", "display" | |
| 90 * \param[out] prp all regression params | |
| 91 * \return 0 if OK, 1 on error | |
| 92 * | |
| 93 * <pre> | |
| 94 * Notes: | |
| 95 * (1) Call this function with the args to the reg test. The first arg | |
| 96 * is the name of the reg test. There are three cases: | |
| 97 * Case 1: | |
| 98 * There is either only one arg, or the second arg is "compare". | |
| 99 * This is the mode in which you run a regression test | |
| 100 * (or a set of them), looking for failures and logging | |
| 101 * the results to a file. The output, which includes | |
| 102 * logging of all reg test failures plus a SUCCESS or | |
| 103 * FAILURE summary for each test, is appended to the file | |
| 104 * "/tmp/lept/reg_results.txt. For this case, as in Case 2, | |
| 105 * the display field in rp is set to FALSE, preventing | |
| 106 * image display. | |
| 107 * Case 2: | |
| 108 * The second arg is "generate". This will cause | |
| 109 * generation of new golden files for the reg test. | |
| 110 * The results of the reg test are not recorded, and | |
| 111 * the display field in rp is set to FALSE. | |
| 112 * Case 3: | |
| 113 * The second arg is "display". The test will run and | |
| 114 * files will be written. Comparisons with golden files | |
| 115 * will not be carried out, so the only notion of success | |
| 116 * or failure is with tests that do not involve golden files. | |
| 117 * The display field in rp is TRUE, and this is used by | |
| 118 * pixDisplayWithTitle(). | |
| 119 * (2) See regutils.h for examples of usage. | |
| 120 * </pre> | |
| 121 */ | |
| 122 l_ok | |
| 123 regTestSetup(l_int32 argc, | |
| 124 char **argv, | |
| 125 L_REGPARAMS **prp) | |
| 126 { | |
| 127 char *testname, *vers; | |
| 128 char errormsg[64]; | |
| 129 L_REGPARAMS *rp; | |
| 130 | |
| 131 if (argc != 1 && argc != 2) { | |
| 132 snprintf(errormsg, sizeof(errormsg), | |
| 133 "Syntax: %s [ [compare] | generate | display ]", argv[0]); | |
| 134 return ERROR_INT(errormsg, __func__, 1); | |
| 135 } | |
| 136 | |
| 137 if ((testname = getRootNameFromArgv0(argv[0])) == NULL) | |
| 138 return ERROR_INT("invalid root", __func__, 1); | |
| 139 | |
| 140 setLeptDebugOK(1); /* required for testing */ | |
| 141 | |
| 142 rp = (L_REGPARAMS *)LEPT_CALLOC(1, sizeof(L_REGPARAMS)); | |
| 143 *prp = rp; | |
| 144 rp->testname = testname; | |
| 145 rp->index = -1; /* increment before each test */ | |
| 146 | |
| 147 /* Initialize to true. A failure in any test is registered | |
| 148 * as a failure of the regression test. */ | |
| 149 rp->success = TRUE; | |
| 150 | |
| 151 /* Make sure the lept/regout subdirectory exists */ | |
| 152 lept_mkdir("lept/regout"); | |
| 153 | |
| 154 /* Only open a stream to a temp file for the 'compare' case */ | |
| 155 if (argc == 1 || !strcmp(argv[1], "compare")) { | |
| 156 rp->mode = L_REG_COMPARE; | |
| 157 rp->tempfile = stringNew("/tmp/lept/regout/regtest_output.txt"); | |
| 158 rp->fp = fopenWriteStream(rp->tempfile, "wb"); | |
| 159 if (rp->fp == NULL) { | |
| 160 rp->success = FALSE; | |
| 161 return ERROR_INT_1("stream not opened for tempfile", | |
| 162 rp->tempfile, __func__, 1); | |
| 163 } | |
| 164 } else if (!strcmp(argv[1], "generate")) { | |
| 165 rp->mode = L_REG_GENERATE; | |
| 166 lept_mkdir("lept/golden"); | |
| 167 } else if (!strcmp(argv[1], "display")) { | |
| 168 rp->mode = L_REG_DISPLAY; | |
| 169 rp->display = TRUE; | |
| 170 } else { | |
| 171 LEPT_FREE(rp); | |
| 172 snprintf(errormsg, sizeof(errormsg), | |
| 173 "Syntax: %s [ [generate] | compare | display ]", argv[0]); | |
| 174 return ERROR_INT(errormsg, __func__, 1); | |
| 175 } | |
| 176 | |
| 177 /* Print out test name and both the leptonica and | |
| 178 * image library versions */ | |
| 179 lept_stderr("\n////////////////////////////////////////////////\n" | |
| 180 "//////////////// %s_reg ///////////////\n" | |
| 181 "////////////////////////////////////////////////\n", | |
| 182 rp->testname); | |
| 183 vers = getLeptonicaVersion(); | |
| 184 lept_stderr("%s : ", vers); | |
| 185 LEPT_FREE(vers); | |
| 186 vers = getImagelibVersions(); | |
| 187 lept_stderr("%s\n", vers); | |
| 188 LEPT_FREE(vers); | |
| 189 | |
| 190 rp->tstart = startTimerNested(); | |
| 191 return 0; | |
| 192 } | |
| 193 | |
| 194 | |
| 195 /*! | |
| 196 * \brief regTestCleanup() | |
| 197 * | |
| 198 * \param[in] rp regression test parameters | |
| 199 * \return 0 if OK, 1 on error | |
| 200 * | |
| 201 * <pre> | |
| 202 * Notes: | |
| 203 * (1) This copies anything written to the temporary file to the | |
| 204 * output file /tmp/lept/reg_results.txt. | |
| 205 * </pre> | |
| 206 */ | |
| 207 l_ok | |
| 208 regTestCleanup(L_REGPARAMS *rp) | |
| 209 { | |
| 210 char result[512]; | |
| 211 char *results_file; /* success/failure output in 'compare' mode */ | |
| 212 char *text, *message; | |
| 213 l_int32 retval; | |
| 214 size_t nbytes; | |
| 215 | |
| 216 if (!rp) | |
| 217 return ERROR_INT("rp not defined", __func__, 1); | |
| 218 | |
| 219 lept_stderr("Time: %7.3f sec\n", stopTimerNested(rp->tstart)); | |
| 220 | |
| 221 /* If generating golden files or running in display mode, release rp */ | |
| 222 if (!rp->fp) { | |
| 223 LEPT_FREE(rp->testname); | |
| 224 LEPT_FREE(rp->tempfile); | |
| 225 LEPT_FREE(rp); | |
| 226 return 0; | |
| 227 } | |
| 228 | |
| 229 /* Compare mode: read back data from temp file */ | |
| 230 fclose(rp->fp); | |
| 231 text = (char *)l_binaryRead(rp->tempfile, &nbytes); | |
| 232 LEPT_FREE(rp->tempfile); | |
| 233 if (!text) { | |
| 234 rp->success = FALSE; | |
| 235 LEPT_FREE(rp->testname); | |
| 236 LEPT_FREE(rp); | |
| 237 return ERROR_INT("text not returned", __func__, 1); | |
| 238 } | |
| 239 | |
| 240 /* Prepare result message */ | |
| 241 if (rp->success) | |
| 242 snprintf(result, sizeof(result), "SUCCESS: %s_reg\n", rp->testname); | |
| 243 else | |
| 244 snprintf(result, sizeof(result), "FAILURE: %s_reg\n", rp->testname); | |
| 245 message = stringJoin(text, result); | |
| 246 LEPT_FREE(text); | |
| 247 results_file = stringNew("/tmp/lept/reg_results.txt"); | |
| 248 fileAppendString(results_file, message); | |
| 249 retval = (rp->success) ? 0 : 1; | |
| 250 LEPT_FREE(results_file); | |
| 251 LEPT_FREE(message); | |
| 252 | |
| 253 LEPT_FREE(rp->testname); | |
| 254 LEPT_FREE(rp); | |
| 255 return retval; | |
| 256 } | |
| 257 | |
| 258 | |
| 259 /*! | |
| 260 * \brief regTestCompareValues() | |
| 261 * | |
| 262 * \param[in] rp regtest parameters | |
| 263 * \param[in] val1 typ. the golden value | |
| 264 * \param[in] val2 typ. the value computed | |
| 265 * \param[in] delta allowed max absolute difference | |
| 266 * \return 0 if OK, 1 on error | |
| 267 * Note: a failure in comparison is not an error | |
| 268 */ | |
| 269 l_ok | |
| 270 regTestCompareValues(L_REGPARAMS *rp, | |
| 271 l_float32 val1, | |
| 272 l_float32 val2, | |
| 273 l_float32 delta) | |
| 274 { | |
| 275 l_float32 diff; | |
| 276 | |
| 277 if (!rp) | |
| 278 return ERROR_INT("rp not defined", __func__, 1); | |
| 279 | |
| 280 rp->index++; | |
| 281 diff = L_ABS(val2 - val1); | |
| 282 | |
| 283 /* Record on failure */ | |
| 284 if (diff > delta) { | |
| 285 if (rp->fp) { | |
| 286 fprintf(rp->fp, | |
| 287 "Failure in %s_reg: value comparison for index %d\n" | |
| 288 "difference = %f but allowed delta = %f\n", | |
| 289 rp->testname, rp->index, diff, delta); | |
| 290 } | |
| 291 lept_stderr("Failure in %s_reg: value comparison for index %d\n" | |
| 292 "difference = %f but allowed delta = %f\n", | |
| 293 rp->testname, rp->index, diff, delta); | |
| 294 rp->success = FALSE; | |
| 295 } | |
| 296 return 0; | |
| 297 } | |
| 298 | |
| 299 | |
| 300 /*! | |
| 301 * \brief regTestCompareStrings() | |
| 302 * | |
| 303 * \param[in] rp regtest parameters | |
| 304 * \param[in] string1 typ. the expected string | |
| 305 * \param[in] bytes1 size of string1 | |
| 306 * \param[in] string2 typ. the computed string | |
| 307 * \param[in] bytes2 size of string2 | |
| 308 * \return 0 if OK, 1 on error | |
| 309 * Note: a failure in comparison is not an error | |
| 310 */ | |
| 311 l_ok | |
| 312 regTestCompareStrings(L_REGPARAMS *rp, | |
| 313 l_uint8 *string1, | |
| 314 size_t bytes1, | |
| 315 l_uint8 *string2, | |
| 316 size_t bytes2) | |
| 317 { | |
| 318 l_int32 same; | |
| 319 char buf[256]; | |
| 320 | |
| 321 if (!rp) | |
| 322 return ERROR_INT("rp not defined", __func__, 1); | |
| 323 | |
| 324 rp->index++; | |
| 325 l_binaryCompare(string1, bytes1, string2, bytes2, &same); | |
| 326 | |
| 327 /* Output on failure */ | |
| 328 if (!same) { | |
| 329 /* Write the two strings to file */ | |
| 330 snprintf(buf, sizeof(buf), "/tmp/lept/regout/string1_%d_%zu", | |
| 331 rp->index, bytes1); | |
| 332 l_binaryWrite(buf, "w", string1, bytes1); | |
| 333 snprintf(buf, sizeof(buf), "/tmp/lept/regout/string2_%d_%zu", | |
| 334 rp->index, bytes2); | |
| 335 l_binaryWrite(buf, "w", string2, bytes2); | |
| 336 | |
| 337 /* Report comparison failure */ | |
| 338 snprintf(buf, sizeof(buf), "/tmp/lept/regout/string*_%d_*", rp->index); | |
| 339 if (rp->fp) { | |
| 340 fprintf(rp->fp, | |
| 341 "Failure in %s_reg: string comp for index %d; " | |
| 342 "written to %s\n", rp->testname, rp->index, buf); | |
| 343 } | |
| 344 lept_stderr("Failure in %s_reg: string comp for index %d; " | |
| 345 "written to %s\n", rp->testname, rp->index, buf); | |
| 346 rp->success = FALSE; | |
| 347 } | |
| 348 return 0; | |
| 349 } | |
| 350 | |
| 351 | |
| 352 /*! | |
| 353 * \brief regTestComparePix() | |
| 354 * | |
| 355 * \param[in] rp regtest parameters | |
| 356 * \param[in] pix1, pix2 to be tested for equality | |
| 357 * \return 0 if OK, 1 on error | |
| 358 * Note: a failure in comparison is not an error | |
| 359 * | |
| 360 * <pre> | |
| 361 * Notes: | |
| 362 * (1) This function compares two pix for equality. On failure, | |
| 363 * this writes to stderr. | |
| 364 * </pre> | |
| 365 */ | |
| 366 l_ok | |
| 367 regTestComparePix(L_REGPARAMS *rp, | |
| 368 PIX *pix1, | |
| 369 PIX *pix2) | |
| 370 { | |
| 371 l_int32 same; | |
| 372 | |
| 373 if (!rp) | |
| 374 return ERROR_INT("rp not defined", __func__, 1); | |
| 375 if (!pix1 || !pix2) { | |
| 376 rp->success = FALSE; | |
| 377 return ERROR_INT("pix1 and pix2 not both defined", __func__, 1); | |
| 378 } | |
| 379 | |
| 380 rp->index++; | |
| 381 pixEqual(pix1, pix2, &same); | |
| 382 | |
| 383 /* Record on failure */ | |
| 384 if (!same) { | |
| 385 if (rp->fp) { | |
| 386 fprintf(rp->fp, "Failure in %s_reg: pix comparison for index %d\n", | |
| 387 rp->testname, rp->index); | |
| 388 } | |
| 389 lept_stderr("Failure in %s_reg: pix comparison for index %d\n", | |
| 390 rp->testname, rp->index); | |
| 391 rp->success = FALSE; | |
| 392 } | |
| 393 return 0; | |
| 394 } | |
| 395 | |
| 396 | |
| 397 /*! | |
| 398 * \brief regTestCompareSimilarPix() | |
| 399 * | |
| 400 * \param[in] rp regtest parameters | |
| 401 * \param[in] pix1, pix2 to be tested for near equality | |
| 402 * \param[in] mindiff minimum pixel difference to be counted; > 0 | |
| 403 * \param[in] maxfract maximum fraction of pixels allowed to have | |
| 404 * diff greater than or equal to mindiff | |
| 405 * \param[in] printstats use 1 to print normalized histogram to stderr | |
| 406 * \return 0 if OK, 1 on error | |
| 407 * Note: a failure in similarity comparison is not an error | |
| 408 * | |
| 409 * <pre> | |
| 410 * Notes: | |
| 411 * (1) This function compares two pix for near equality. On failure, | |
| 412 * this writes to stderr. | |
| 413 * (2) The pix are similar if the fraction of non-conforming pixels | |
| 414 * does not exceed %maxfract. Pixels are non-conforming if | |
| 415 * the difference in pixel values equals or exceeds %mindiff. | |
| 416 * Typical values might be %mindiff = 15 and %maxfract = 0.01. | |
| 417 * (3) The input images must have the same size and depth. The | |
| 418 * pixels for comparison are typically subsampled from the images. | |
| 419 * (4) Normally, use %printstats = 0. In debugging mode, to see | |
| 420 * the relation between %mindiff and the minimum value of | |
| 421 * %maxfract for success, set this to 1. | |
| 422 * </pre> | |
| 423 */ | |
| 424 l_ok | |
| 425 regTestCompareSimilarPix(L_REGPARAMS *rp, | |
| 426 PIX *pix1, | |
| 427 PIX *pix2, | |
| 428 l_int32 mindiff, | |
| 429 l_float32 maxfract, | |
| 430 l_int32 printstats) | |
| 431 { | |
| 432 l_int32 w, h, factor, similar; | |
| 433 | |
| 434 if (!rp) | |
| 435 return ERROR_INT("rp not defined", __func__, 1); | |
| 436 if (!pix1 || !pix2) { | |
| 437 rp->success = FALSE; | |
| 438 return ERROR_INT("pix1 and pix2 not both defined", __func__, 1); | |
| 439 } | |
| 440 | |
| 441 rp->index++; | |
| 442 pixGetDimensions(pix1, &w, &h, NULL); | |
| 443 factor = L_MAX(w, h) / 400; | |
| 444 factor = L_MAX(1, L_MIN(factor, 4)); /* between 1 and 4 */ | |
| 445 pixTestForSimilarity(pix1, pix2, factor, mindiff, maxfract, 0.0, | |
| 446 &similar, printstats); | |
| 447 | |
| 448 /* Record on failure */ | |
| 449 if (!similar) { | |
| 450 if (rp->fp) { | |
| 451 fprintf(rp->fp, | |
| 452 "Failure in %s_reg: pix similarity comp for index %d\n", | |
| 453 rp->testname, rp->index); | |
| 454 } | |
| 455 lept_stderr("Failure in %s_reg: pix similarity comp for index %d\n", | |
| 456 rp->testname, rp->index); | |
| 457 rp->success = FALSE; | |
| 458 } | |
| 459 return 0; | |
| 460 } | |
| 461 | |
| 462 | |
| 463 /*! | |
| 464 * \brief regTestCheckFile() | |
| 465 * | |
| 466 * \param[in] rp regtest parameters | |
| 467 * \param[in] localname name of output file from reg test | |
| 468 * \return 0 if OK, 1 on error | |
| 469 * Note: a failure in comparison is not an error | |
| 470 * | |
| 471 * <pre> | |
| 472 * Notes: | |
| 473 * (1) This function does one of three things, depending on the mode: | |
| 474 * * "generate": makes a "golden" file as a copy of %localname. | |
| 475 * * "compare": compares %localname contents with the golden file | |
| 476 * * "display": this does nothing | |
| 477 * (2) The canonical format of the golden filenames is: | |
| 478 * /tmp/lept/golden/[root of main name]_golden.[index]. | |
| 479 * [ext of localname] | |
| 480 * e.g., | |
| 481 * /tmp/lept/golden/maze_golden.0.png | |
| 482 * (3) The local file can be made in any subdirectory of /tmp/lept, | |
| 483 * including /tmp/lept/regout/. | |
| 484 * (4) It is important to add an extension to the local name, such as | |
| 485 * /tmp/lept/maze/file1.png (extension ".png") | |
| 486 * because the extension is added to the name of the golden file. | |
| 487 * </pre> | |
| 488 */ | |
| 489 l_ok | |
| 490 regTestCheckFile(L_REGPARAMS *rp, | |
| 491 const char *localname) | |
| 492 { | |
| 493 char *ext; | |
| 494 char namebuf[256]; | |
| 495 l_int32 ret, same, format; | |
| 496 PIX *pix1, *pix2; | |
| 497 | |
| 498 if (!rp) | |
| 499 return ERROR_INT("rp not defined", __func__, 1); | |
| 500 if (!localname) { | |
| 501 rp->success = FALSE; | |
| 502 return ERROR_INT("local name not defined", __func__, 1); | |
| 503 } | |
| 504 if (rp->mode != L_REG_GENERATE && rp->mode != L_REG_COMPARE && | |
| 505 rp->mode != L_REG_DISPLAY) { | |
| 506 rp->success = FALSE; | |
| 507 return ERROR_INT("invalid mode", __func__, 1); | |
| 508 } | |
| 509 rp->index++; | |
| 510 | |
| 511 /* If display mode, no generation and no testing */ | |
| 512 if (rp->mode == L_REG_DISPLAY) return 0; | |
| 513 | |
| 514 /* Generate the golden file name; used in 'generate' and 'compare' */ | |
| 515 splitPathAtExtension(localname, NULL, &ext); | |
| 516 snprintf(namebuf, sizeof(namebuf), "/tmp/lept/golden/%s_golden.%02d%s", | |
| 517 rp->testname, rp->index, ext); | |
| 518 LEPT_FREE(ext); | |
| 519 | |
| 520 /* Generate mode. No testing. */ | |
| 521 if (rp->mode == L_REG_GENERATE) { | |
| 522 /* Save the file as a golden file */ | |
| 523 ret = fileCopy(localname, namebuf); | |
| 524 #if 0 /* Enable for details on writing of golden files */ | |
| 525 if (!ret) { | |
| 526 char *local = genPathname(localname, NULL); | |
| 527 char *golden = genPathname(namebuf, NULL); | |
| 528 L_INFO("Copy: %s to %s\n", __func__, local, golden); | |
| 529 LEPT_FREE(local); | |
| 530 LEPT_FREE(golden); | |
| 531 } | |
| 532 #endif | |
| 533 return ret; | |
| 534 } | |
| 535 | |
| 536 /* Compare mode: test and record on failure. This can be used | |
| 537 * for all image formats, as well as for all files of serialized | |
| 538 * data, such as boxa, pta, etc. In all cases except for | |
| 539 * GIF compressed images, we compare the files to see if they | |
| 540 * are identical. GIF doesn't support RGB images; to write | |
| 541 * a 32 bpp RGB image in GIF, we do a lossy quantization to | |
| 542 * 256 colors, so the cycle read-RGB/write-GIF is not idempotent. | |
| 543 * And although the read/write cycle for GIF images with bpp <= 8 | |
| 544 * is idempotent in the image pixels, it is not idempotent in the | |
| 545 * actual file bytes; tests comparing file bytes before and after | |
| 546 * a GIF read/write cycle will fail. So for GIF we uncompress | |
| 547 * the two images and compare the actual pixels. PNG is both | |
| 548 * lossless and idempotent in file bytes on read/write, so it is | |
| 549 * not necessary to compare pixels. (Comparing pixels requires | |
| 550 * decompression, and thus would increase the regression test | |
| 551 * time. JPEG is lossy and not idempotent in the image pixels, | |
| 552 * so no tests are constructed that would require it. */ | |
| 553 findFileFormat(localname, &format); | |
| 554 if (format == IFF_GIF) { | |
| 555 same = 0; | |
| 556 pix1 = pixRead(localname); | |
| 557 pix2 = pixRead(namebuf); | |
| 558 pixEqual(pix1, pix2, &same); | |
| 559 pixDestroy(&pix1); | |
| 560 pixDestroy(&pix2); | |
| 561 } else { | |
| 562 filesAreIdentical(localname, namebuf, &same); | |
| 563 } | |
| 564 if (!same) { | |
| 565 fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n", | |
| 566 rp->testname, rp->index, localname, namebuf); | |
| 567 lept_stderr("Failure in %s_reg, index %d: comparing %s with %s\n", | |
| 568 rp->testname, rp->index, localname, namebuf); | |
| 569 rp->success = FALSE; | |
| 570 } | |
| 571 | |
| 572 return 0; | |
| 573 } | |
| 574 | |
| 575 | |
| 576 /*! | |
| 577 * \brief regTestCompareFiles() | |
| 578 * | |
| 579 * \param[in] rp regtest parameters | |
| 580 * \param[in] index1 of one output file from reg test | |
| 581 * \param[in] index2 of another output file from reg test | |
| 582 * \return 0 if OK, 1 on error | |
| 583 * Note: a failure in comparison is not an error | |
| 584 * | |
| 585 * <pre> | |
| 586 * Notes: | |
| 587 * (1) This only does something in "compare" mode. | |
| 588 * (2) The canonical format of the golden filenames is: | |
| 589 * /tmp/lept/golden/[root of main name]_golden.[index]. | |
| 590 * [ext of localname] | |
| 591 * e.g., | |
| 592 * /tmp/lept/golden/maze_golden.0.png | |
| 593 * </pre> | |
| 594 */ | |
| 595 l_ok | |
| 596 regTestCompareFiles(L_REGPARAMS *rp, | |
| 597 l_int32 index1, | |
| 598 l_int32 index2) | |
| 599 { | |
| 600 char *name1, *name2; | |
| 601 char namebuf[256]; | |
| 602 l_int32 same; | |
| 603 SARRAY *sa; | |
| 604 | |
| 605 if (!rp) | |
| 606 return ERROR_INT("rp not defined", __func__, 1); | |
| 607 if (index1 < 0 || index2 < 0) { | |
| 608 rp->success = FALSE; | |
| 609 return ERROR_INT("index1 and/or index2 is negative", __func__, 1); | |
| 610 } | |
| 611 if (index1 == index2) { | |
| 612 rp->success = FALSE; | |
| 613 return ERROR_INT("index1 must differ from index2", __func__, 1); | |
| 614 } | |
| 615 | |
| 616 rp->index++; | |
| 617 if (rp->mode != L_REG_COMPARE) return 0; | |
| 618 | |
| 619 /* Generate the golden file names */ | |
| 620 snprintf(namebuf, sizeof(namebuf), "%s_golden.%02d", rp->testname, index1); | |
| 621 sa = getSortedPathnamesInDirectory("/tmp/lept/golden", namebuf, 0, 0); | |
| 622 if (sarrayGetCount(sa) != 1) { | |
| 623 sarrayDestroy(&sa); | |
| 624 rp->success = FALSE; | |
| 625 L_ERROR("golden file %s not found\n", __func__, namebuf); | |
| 626 return 1; | |
| 627 } | |
| 628 name1 = sarrayGetString(sa, 0, L_COPY); | |
| 629 sarrayDestroy(&sa); | |
| 630 | |
| 631 snprintf(namebuf, sizeof(namebuf), "%s_golden.%02d", rp->testname, index2); | |
| 632 sa = getSortedPathnamesInDirectory("/tmp/lept/golden", namebuf, 0, 0); | |
| 633 if (sarrayGetCount(sa) != 1) { | |
| 634 sarrayDestroy(&sa); | |
| 635 rp->success = FALSE; | |
| 636 LEPT_FREE(name1); | |
| 637 L_ERROR("golden file %s not found\n", __func__, namebuf); | |
| 638 return 1; | |
| 639 } | |
| 640 name2 = sarrayGetString(sa, 0, L_COPY); | |
| 641 sarrayDestroy(&sa); | |
| 642 | |
| 643 /* Test and record on failure */ | |
| 644 filesAreIdentical(name1, name2, &same); | |
| 645 if (!same) { | |
| 646 fprintf(rp->fp, | |
| 647 "Failure in %s_reg, index %d: comparing %s with %s\n", | |
| 648 rp->testname, rp->index, name1, name2); | |
| 649 lept_stderr("Failure in %s_reg, index %d: comparing %s with %s\n", | |
| 650 rp->testname, rp->index, name1, name2); | |
| 651 rp->success = FALSE; | |
| 652 } | |
| 653 | |
| 654 LEPT_FREE(name1); | |
| 655 LEPT_FREE(name2); | |
| 656 return 0; | |
| 657 } | |
| 658 | |
| 659 | |
| 660 /*! | |
| 661 * \brief regTestWritePixAndCheck() | |
| 662 * | |
| 663 * \param[in] rp regtest parameters | |
| 664 * \param[in] pix to be written | |
| 665 * \param[in] format of output pix | |
| 666 * \return 0 if OK, 1 on error | |
| 667 * Note: a failure in comparison is not an error | |
| 668 * | |
| 669 * <pre> | |
| 670 * Notes: | |
| 671 * (1) This function makes it easy to write the pix in a numbered | |
| 672 * sequence of files, and either to: | |
| 673 * (a) write the golden file ("generate" arg to regression test) | |
| 674 * (b) make a local file and "compare" with the golden file | |
| 675 * (c) make a local file and "display" the results | |
| 676 * (2) The canonical format of the local filename is: | |
| 677 * /tmp/lept/regout/[root of main name].[count].[format extension] | |
| 678 * e.g., for scale_reg, | |
| 679 * /tmp/lept/regout/scale.0.png | |
| 680 * The golden file name mirrors this in the usual way. | |
| 681 * (3) The check is done between the written files, which requires | |
| 682 * the files to be identical. The exception is for GIF, which | |
| 683 * only requires that all pixels in the decoded pix are identical. | |
| 684 * </pre> | |
| 685 */ | |
| 686 l_ok | |
| 687 regTestWritePixAndCheck(L_REGPARAMS *rp, | |
| 688 PIX *pix, | |
| 689 l_int32 format) | |
| 690 { | |
| 691 char namebuf[256]; | |
| 692 | |
| 693 if (!rp) | |
| 694 return ERROR_INT("rp not defined", __func__, 1); | |
| 695 if (!pix) { | |
| 696 rp->success = FALSE; | |
| 697 return ERROR_INT("pix not defined", __func__, 1); | |
| 698 } | |
| 699 if (format < 0 || format >= NumImageFileFormatExtensions) { | |
| 700 rp->success = FALSE; | |
| 701 return ERROR_INT("invalid format", __func__, 1); | |
| 702 } | |
| 703 | |
| 704 /* Use bmp format for testing if library for requested | |
| 705 * format for jpeg, png or tiff is not available */ | |
| 706 changeFormatForMissingLib(&format); | |
| 707 | |
| 708 /* Generate the local file name */ | |
| 709 snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s", | |
| 710 rp->testname, rp->index + 1, ImageFileFormatExtensions[format]); | |
| 711 | |
| 712 /* Write the local file */ | |
| 713 if (pixGetDepth(pix) < 8) | |
| 714 pixSetPadBits(pix, 0); | |
| 715 pixWrite(namebuf, pix, format); | |
| 716 | |
| 717 /* Either write the golden file ("generate") or check the | |
| 718 local file against an existing golden file ("compare") */ | |
| 719 regTestCheckFile(rp, namebuf); | |
| 720 | |
| 721 return 0; | |
| 722 } | |
| 723 | |
| 724 | |
| 725 /*! | |
| 726 * \brief regTestWriteDataAndCheck() | |
| 727 * | |
| 728 * \param[in] rp regtest parameters | |
| 729 * \param[in] data to be written | |
| 730 * \param[in] nbytes of data to be written | |
| 731 * \param[in] ext filename extension (e.g.: "ba", "pta") | |
| 732 * \return 0 if OK, 1 on error | |
| 733 * Note: a failure in comparison is not an error | |
| 734 * | |
| 735 * <pre> | |
| 736 * Notes: | |
| 737 * (1) This function makes it easy to write data in a numbered | |
| 738 * sequence of files, and either to: | |
| 739 * (a) write the golden file ("generate" arg to regression test) | |
| 740 * (b) make a local file and "compare" with the golden file | |
| 741 * (c) make a local file and "display" the results | |
| 742 * (2) The canonical format of the local filename is: | |
| 743 * /tmp/lept/regout/[root of main name].[count].[ext] | |
| 744 * e.g., for the first boxaa in quadtree_reg, | |
| 745 * /tmp/lept/regout/quadtree.0.baa | |
| 746 * The golden file name mirrors this in the usual way. | |
| 747 * (3) The data can be anything. It is most useful for serialized | |
| 748 * output of data, such as boxa, pta, etc. | |
| 749 * (4) The file extension is arbitrary. It is included simply | |
| 750 * to make the content type obvious when examining written files. | |
| 751 * (5) The check is done between the written files, which requires | |
| 752 * the files to be identical. | |
| 753 * </pre> | |
| 754 */ | |
| 755 l_ok | |
| 756 regTestWriteDataAndCheck(L_REGPARAMS *rp, | |
| 757 void *data, | |
| 758 size_t nbytes, | |
| 759 const char *ext) | |
| 760 { | |
| 761 char namebuf[256]; | |
| 762 | |
| 763 if (!rp) | |
| 764 return ERROR_INT("rp not defined", __func__, 1); | |
| 765 if (!data || nbytes == 0) { | |
| 766 rp->success = FALSE; | |
| 767 return ERROR_INT("data not defined or size == 0", __func__, 1); | |
| 768 } | |
| 769 | |
| 770 /* Generate the local file name */ | |
| 771 snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s", | |
| 772 rp->testname, rp->index + 1, ext); | |
| 773 | |
| 774 /* Write the local file */ | |
| 775 l_binaryWrite(namebuf, "w", data, nbytes); | |
| 776 | |
| 777 /* Either write the golden file ("generate") or check the | |
| 778 local file against an existing golden file ("compare") */ | |
| 779 regTestCheckFile(rp, namebuf); | |
| 780 return 0; | |
| 781 } | |
| 782 | |
| 783 | |
| 784 /*! | |
| 785 * \brief regTestGenLocalFilename() | |
| 786 * | |
| 787 * \param[in] rp regtest parameters | |
| 788 * \param[in] index use -1 for current index | |
| 789 * \param[in] format of image; e.g., IFF_PNG | |
| 790 * \return filename if OK, or NULL on error | |
| 791 * | |
| 792 * <pre> | |
| 793 * Notes: | |
| 794 * (1) This is used to get the name of a file in the regout | |
| 795 * subdirectory, that has been made and is used to test against | |
| 796 * the golden file. You can either specify a particular index | |
| 797 * value, or with %index == -1, this returns the most recently | |
| 798 * written file. The latter case lets you read a pix from a | |
| 799 * file that has just been written with regTestWritePixAndCheck(), | |
| 800 * which is useful for testing formatted read/write functions. | |
| 801 * | |
| 802 * </pre> | |
| 803 */ | |
| 804 char * | |
| 805 regTestGenLocalFilename(L_REGPARAMS *rp, | |
| 806 l_int32 index, | |
| 807 l_int32 format) | |
| 808 { | |
| 809 char buf[64]; | |
| 810 l_int32 ind; | |
| 811 | |
| 812 if (!rp) | |
| 813 return (char *)ERROR_PTR("rp not defined", __func__, NULL); | |
| 814 | |
| 815 ind = (index >= 0) ? index : rp->index; | |
| 816 snprintf(buf, sizeof(buf), "/tmp/lept/regout/%s.%02d.%s", | |
| 817 rp->testname, ind, ImageFileFormatExtensions[format]); | |
| 818 return stringNew(buf); | |
| 819 } | |
| 820 | |
| 821 | |
| 822 /*! | |
| 823 * \brief getRootNameFromArgv0() | |
| 824 * | |
| 825 * \param[in] argv0 | |
| 826 * \return root name without the '_reg', or NULL on error | |
| 827 * | |
| 828 * <pre> | |
| 829 * Notes: | |
| 830 * (1) For example, from psioseg_reg, we want to extract | |
| 831 * just 'psioseg' as the root. | |
| 832 * (2) In unix with autotools, the executable is not X, | |
| 833 * but ./.libs/lt-X. So in addition to stripping out the | |
| 834 * last 4 characters of the tail, we have to check for | |
| 835 * the '-' and strip out the "lt-" prefix if we find it. | |
| 836 * </pre> | |
| 837 */ | |
| 838 static char * | |
| 839 getRootNameFromArgv0(const char *argv0) | |
| 840 { | |
| 841 l_int32 len; | |
| 842 char *root; | |
| 843 | |
| 844 splitPathAtDirectory(argv0, NULL, &root); | |
| 845 if ((len = strlen(root)) <= 4) { | |
| 846 LEPT_FREE(root); | |
| 847 return (char *)ERROR_PTR("invalid argv0; too small", __func__, NULL); | |
| 848 } | |
| 849 | |
| 850 #ifndef _WIN32 | |
| 851 { | |
| 852 char *newroot; | |
| 853 l_int32 loc; | |
| 854 if (stringFindSubstr(root, "-", &loc)) { | |
| 855 newroot = stringNew(root + loc + 1); /* strip out "lt-" */ | |
| 856 LEPT_FREE(root); | |
| 857 root = newroot; | |
| 858 len = strlen(root); | |
| 859 } | |
| 860 len -= 4; /* remove the "_reg" suffix */ | |
| 861 } | |
| 862 #else | |
| 863 if (strstr(root, ".exe") != NULL) | |
| 864 len -= 4; | |
| 865 if (strstr(root, "_reg") == root + len - 4) | |
| 866 len -= 4; | |
| 867 #endif /* ! _WIN32 */ | |
| 868 | |
| 869 root[len] = '\0'; /* terminate */ | |
| 870 return root; | |
| 871 } |
